<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
<channel>
<title><![CDATA[ Relay ]]></title>
<description><![CDATA[ Your go-to source for the latest insights, trends, and innovations in the world of ServiceNow ]]></description>
<link>https://relay.semaphorepartners.com</link>
<image>
    <url>https://relay.semaphorepartners.com/favicon.png</url>
    <title>Relay</title>
    <link>https://relay.semaphorepartners.com</link>
</image>
<lastBuildDate>Fri, 15 May 2026 22:22:39 -0400</lastBuildDate>
<atom:link href="https://relay.semaphorepartners.com" rel="self" type="application/rss+xml"/>
<ttl>60</ttl>

    <item>
        <title><![CDATA[ Your first widget ]]></title>
        <description><![CDATA[ The fastest way to understand sn_aiux is to bring something familiar over from Service Portal and watch it run inside the new framework. This walkthrough takes Cool Clock — an analog-clock widget that ships out of the box on every ServiceNow instance — and surfaces it inside an sn_aiux experience ]]></description>
        <link>https://relay.semaphorepartners.com/aiux-docs/your-first-widget/</link>
        <guid isPermaLink="false">6a0363fdfc421e00014ea34c</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Tue, 12 May 2026 13:38:10 -0400</pubDate>
        <media:content url="" medium="image"/>
        <content:encoded><![CDATA[ <p>The fastest way to understand&nbsp;<code>sn_aiux</code>&nbsp;is to bring something familiar over from Service Portal and watch it run inside the new framework. This walkthrough takes&nbsp;<strong>Cool Clock</strong>&nbsp;— an analog-clock widget that ships out of the box on every ServiceNow instance — and surfaces it inside an&nbsp;<code>sn_aiux</code>&nbsp;experience with no modifications to the original widget.</p><p>For a from-scratch native widget, see&nbsp;<a href="https://file+.vscode-resource.vscode-cdn.net/Users/jonathanjacob/Documents/Git/claude-skills/aiux-blog-screenshots/docs/getting-started/your-first-widget.md?ref=relay.semaphorepartners.com#going-native">Going native</a>&nbsp;at the end.</p><h2 id="the-cool-clock-%E2%80%94-what-were-working-with">The Cool Clock — what we're working with</h2><p>The OOB Service Portal widget has an&nbsp;<code>id</code>&nbsp;of&nbsp;<code>widget-cool-clock</code>&nbsp;and exactly two configurable options:</p><pre><code class="language-json">[
  { "name": "zone",    "default_value": "America/Los_Angeles",
    "section": "Data",         "label": "TimeZone",          "type": "string" },
  { "name": "c_color", "default_value": "red",
    "section": "Presentation", "label": "Second hand color", "type": "string" }
]
</code></pre><p>We are not going to rewrite Cool Clock. The whole point of this exercise is that we&nbsp;<em>don't have to</em>. The widget already exists, it already works in Service Portal, and the Service Portal Bridge will run it inside&nbsp;<code>sn_aiux</code>&nbsp;unchanged. All we're building is the&nbsp;<code>sn_aiux</code>&nbsp;adapter.</p><h2 id="step-1-%E2%80%94-create-the-adapter">Step 1 — create the adapter</h2><p>Navigate to&nbsp;<strong><code>/aiux/builder/widgets</code></strong>&nbsp;and click&nbsp;<strong>Create new widget</strong>.</p><ul><li><strong>Custom element name (id):</strong>&nbsp;<code>cool-clock-aix</code></li><li><strong>Widget name:</strong>&nbsp;Cool Clock</li><li><strong>Description:</strong>&nbsp;Bridges the OOB&nbsp;<code>widget-cool-clock</code>&nbsp;Service Portal widget into an&nbsp;<code>sn_aiux</code>&nbsp;experience.</li><li><strong>Category:</strong>&nbsp;custom</li></ul><p>Set the&nbsp;<strong>Component</strong>&nbsp;field to:</p><pre><code class="language-js">import { html } from 'lit';
import { AIUXWidgetElement } from '@servicenow/aiux-components-core';

class CoolClockAix extends AIUXWidgetElement {
  static properties = {
    zone:    { type: String },
    c_color: { type: String },
  };

  createRenderRoot() { return this; }

  constructor() {
    super();
    this.zone = 'America/Los_Angeles';
    this.c_color = 'red';
  }

  render() {
    const options = {
      zone:    this.zone,
      c_color: this.c_color,
    };
    return html`
      &lt;aiux-angular-element
        .widgetId=${'widget-cool-clock'}
        .options=${options}&gt;
      &lt;/aiux-angular-element&gt;`;
  }
}
</code></pre><p>Set the&nbsp;<strong>Input schema</strong>&nbsp;field to mirror the SP widget's options so the Builder UI surfaces them:</p><pre><code class="language-json">{
  "zone":    { "type": "String", "description": "TimeZone (e.g. America/Los_Angeles)" },
  "c_color": { "type": "String", "description": "Second hand color" }
}
</code></pre><p>Leave the&nbsp;<strong>Server script</strong>&nbsp;field as the empty IIFE. The original&nbsp;<code>widget-cool-clock</code>&nbsp;already has its own server script and the bridge will run it.</p><h2 id="step-2-%E2%80%94-drop-it-on-a-page-or-preview-it">Step 2 — drop it on a page or preview it</h2><p>Open a&nbsp;<code>sys_aix_page</code>&nbsp;in your experience and add a&nbsp;<code>cool-clock-aix</code>&nbsp;widget instance. Configure&nbsp;<code>zone</code>&nbsp;and&nbsp;<code>c_color</code>&nbsp;from the inline form.</p><p>Alternatively - just click the preview tab.</p><h2 id="step-3-%E2%80%94-see-it-run">Step 3 — see it run</h2><p>The Lit wrapper mounts. The framework spins up&nbsp;<code>&lt;aiux-angular-element&gt;</code>, which boots the embedded AngularJS Service Portal runtime inside the Lit DOM, looks up&nbsp;<code>widget-cool-clock</code>&nbsp;by its&nbsp;<code>sp_widget.id</code>, runs that widget's server script with the supplied&nbsp;<code>options</code>, and renders the existing template inside your&nbsp;<code>sn_aiux</code>&nbsp;page. The clock ticks. The second hand is the color you asked for. The timezone is the one you asked for. Nothing about the widget itself changed.</p><p>That's the whole pattern.&nbsp;<strong>One server script, written once. Twenty lines of Lit adapter. Zero rewrites for the actual widget logic.</strong>&nbsp;Multiply that by every widget in your existing portal and you can see why the bridge is a bigger deal than the rest of the framework combined.</p><p>For the mechanism in detail and the three OOB widgets that ship using this pattern, see&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/service-portal-bridge/">Widgets → Service Portal Bridge</a>.</p><hr><h2 id="going-native">Going native</h2><p>The Cool Clock is the safe story — bring what you have, get it on the screen, ship. The other half of the pitch is what happens when you write a widget&nbsp;<em>natively</em>&nbsp;against&nbsp;<code>sn_aiux</code>. For that, build the&nbsp;<code>$aiux</code>&nbsp;showcase: a small Lit widget whose server script exercises each of the four documented methods on&nbsp;<code>$aiux</code>&nbsp;and renders the live return values with DaisyUI.</p><p>It exercises every layer in one component:</p><ul><li><strong>Server script</strong>&nbsp;— calls&nbsp;<code>$aiux.getParameter</code>,&nbsp;<code>$aiux.getPathParameter</code>,&nbsp;<code>$aiux.getSearchParameter</code>, and&nbsp;<code>$aiux.getWidget</code>&nbsp;and packages the live results.</li><li><strong>Option schema</strong>&nbsp;— a couple of configurable inputs (the parameter name to probe, the widget id to compose).</li><li><strong>Lit + DaisyUI</strong>&nbsp;— composes&nbsp;<code>&lt;aiux-alert-message&gt;</code>,&nbsp;<code>aiux-table</code>,&nbsp;<code>aiux-badge</code>&nbsp;to render the results.</li><li><strong>client_tools</strong>&nbsp;— declares&nbsp;<code>setProbeKey({ key })</code>&nbsp;so an AI agent can rerun the probes against a different parameter name.</li></ul><p>The full source is in&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/ai-integration/">Widgets → AI Integration</a>.</p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Installing ]]></title>
        <description><![CDATA[ sn_aiux ships on Zurich Patch 9. To light it up, you install three store apps from the ServiceNow Store:

 * sn_aiux_ia_config — AI Experience Framework Components for Now Assist Setup
 * sn_aiux_builder — the Builder app itself, served at /aiux/builder/...

AI Experience Framework Builder - ServiceNow StoreServiceNow ]]></description>
        <link>https://relay.semaphorepartners.com/aiux-docs/installing/</link>
        <guid isPermaLink="false">6a036024fc421e00014ea32b</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Tue, 12 May 2026 13:18:43 -0400</pubDate>
        <media:content url="" medium="image"/>
        <content:encoded><![CDATA[ <p><code>sn_aiux</code>&nbsp;ships on&nbsp;<strong>Zurich Patch 9</strong>. To light it up, you install three store apps from the ServiceNow Store:</p><ul><li><strong><code>sn_aiux_ia_config</code></strong>&nbsp;— AI Experience Framework Components for Now Assist Setup</li><li><a href="https://store.servicenow.com/store/app/236c652b972c4f90f17933e11153afd1?ref=relay.semaphorepartners.com" rel="noreferrer"><strong><code>sn_aiux_builder</code></strong></a>&nbsp;— the Builder app itself, served at&nbsp;<code>/aiux/builder/...</code></li></ul><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://store.servicenow.com/store/app/236c652b972c4f90f17933e11153afd1?ref=relay.semaphorepartners.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">AI Experience Framework Builder - ServiceNow Store</div><div class="kg-bookmark-description">ServiceNow App Store: AI Experience Framework Builder by ServiceNow - Widget creation and playground experience for ServiceNow AIX widgets</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/icon/favicon-1.svg" alt=""><span class="kg-bookmark-author">ServiceNow Store</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/thumbnail/servicenow-logo-header-1.svg" alt="" onerror="this.style.display = 'none'"></div></a></figure><ul><li><a href="https://store.servicenow.com/store/app/947ced2b4760cf904db76411516d43b5?ref=relay.semaphorepartners.com" rel="noreferrer"><strong><code>sn_aiux_components</code></strong></a>&nbsp;— the OOB widget library (Form, Catalog Item, Order Guide, Activity Stream, Breakout, etc.)</li></ul><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://store.servicenow.com/store/app/947ced2b4760cf904db76411516d43b5?ref=relay.semaphorepartners.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">AI Experience Framework Components - ServiceNow Store</div><div class="kg-bookmark-description">ServiceNow App Store: AI Experience Framework Components by ServiceNow - Common experience components for the AI Experience Framework</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/icon/favicon-2.svg" alt=""><span class="kg-bookmark-author">ServiceNow Store</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/thumbnail/servicenow-logo-header-2.svg" alt="" onerror="this.style.display = 'none'"></div></a></figure><p>All three must be installed and activated before any&nbsp;<code>sys_aix_*</code>&nbsp;records are addressable.</p><h2 id="verifying-the-install">Verifying the install</h2><p>Once the apps are present, the Application Navigator gets a new Application called&nbsp;<strong>AI Experience Framework (AIUX)</strong>&nbsp;with sub-modules for:</p><ul><li>Experiences <code>sys_aix_experience</code></li><li>Pages <code>sys_aix_page</code></li><li>Containers <code>sys_aix_container</code></li><li>Widgets <code>sys_aix_widget</code></li><li>Widget Instances <code>sys_aix_widget_instance</code></li><li>Widget Dependencies <code>sys_aix_dependency</code></li><li>Dashboards <code>sys_aix_dashboard</code></li><li>Menus <code>sys_aix_menu</code></li><li>Themes <code>sys_aix_theme</code></li></ul><p>Anyone who's spent time in the Service Portal application navigator will recognize the shape — it's deliberately the same. ServiceNow has clearly internalized that the path to adoption runs through familiarity, and it built the navigation accordingly.</p><p>You can also confirm the install by hitting the Builder directly:&nbsp;<code>https://&lt;your-instance&gt;.service-now.com/aiux/builder/widgets</code>. If you see the widget catalog, the framework is live.</p><p>If you install <a href="https://store.servicenow.com/store/app/bcc03d674720c7d0cbbce551336d43a5?ref=relay.semaphorepartners.com" rel="noreferrer">Employee Slate </a>- you can hit this directly via: <a href="https://<your-instance>.service-now.com/aiux/employeeslate/home"><code>https://&lt;your-instance&gt;.service-now.com/aiux/employeeslate/home</code></a></p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://store.servicenow.com/store/app/bcc03d674720c7d0cbbce551336d43a5?ref=relay.semaphorepartners.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Employee Slate Core - ServiceNow Store</div><div class="kg-bookmark-description">ServiceNow App Store: Employee Slate Core by ServiceNow - The AI-native employee experience for any service, task, and approval.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/icon/favicon-3.svg" alt=""><span class="kg-bookmark-author">ServiceNow Store</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/thumbnail/servicenow-logo-header-3.svg" alt="" onerror="this.style.display = 'none'"></div></a></figure><h2 id="permissions">Permissions</h2><p>The Builder UI requires the&nbsp;<code>aix_widget_admin</code>&nbsp;role to create or edit widgets, and&nbsp;<code>aix_canvas_admin</code>&nbsp;to edit dashboards. Plain&nbsp;<code>admin</code>&nbsp;is sufficient for both.</p><h2 id="earlier-patches">Earlier patches</h2><p><code>sn_aiux</code>&nbsp;is not present before Zurich Patch 9. The bundle (<code>/sncapps/aix/assets/...</code>), the&nbsp;<code>sys_aix_*</code>&nbsp;tables, and the&nbsp;<code>/aiux/&lt;suffix&gt;/&lt;page&gt;</code>&nbsp;URL pattern all rely on platform plumbing that arrived with Zurich P9. If you're on an earlier patch, the store apps will install but the runtime won't resolve.</p><h2 id="what-to-do-next">What to do next</h2><p>Try&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/your-first-widget/">Your first widget</a>&nbsp;— a walkthrough that takes a real out-of-the-box Service Portal widget and surfaces it inside an&nbsp;<code>sn_aiux</code>&nbsp;experience in roughly twenty lines of Lit.</p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Server Script ]]></title>
        <description><![CDATA[ An sn_aiux widget&#39;s server script runs in the same Rhino engine as a Service Portal sp_widget server script, with the same IIFE signature:

(function(data, options, input) {
  // ...
})(data, options, input);


Same JavaScript, same gs.* / GlideRecord / GlideAggregate / GlideDateTime surface you&#39;ve used for a decade. If ]]></description>
        <link>https://relay.semaphorepartners.com/aiux-docs/server-script/</link>
        <guid isPermaLink="false">6a033f182c447200013e12b9</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Tue, 12 May 2026 10:54:47 -0400</pubDate>
        <media:content url="" medium="image"/>
        <content:encoded><![CDATA[ <p>An sn_aiux widget's server script runs in the same Rhino engine as a Service Portal&nbsp;<code>sp_widget</code>&nbsp;server script, with the same IIFE signature:</p><pre><code class="language-js">(function(data, options, input) {
  // ...
})(data, options, input);
</code></pre><p>Same JavaScript, same&nbsp;<code>gs.*</code>&nbsp;/&nbsp;<code>GlideRecord</code>&nbsp;/&nbsp;<code>GlideAggregate</code>&nbsp;/&nbsp;<code>GlideDateTime</code>&nbsp;surface you've used for a decade. If you've written Service Portal server scripts, you already know 95% of what runs here. This page only covers the 5% that's new or worth re-stating in an sn_aiux context. For the full reference of standard server APIs, see&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/intellisense/">Reference → Intellisense dump</a>.</p><hr><h2 id="server-script-globals">Server-script globals</h2><p>Three globals are populated for you on every invocation:</p>
<!--kg-card-begin: html-->
<table data-line="18" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="18" class="code-line" dir="auto" style="position: relative;"><tr data-line="18" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Global</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="20" class="code-line" dir="auto" style="position: relative;"><tr data-line="20" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">data</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Output object. Populated server-side, becomes<span>&nbsp;</span><code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">this.data</code><span>&nbsp;</span>on the client.</td></tr><tr data-line="21" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">options</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">The widget instance's configured options. Shape mirrors the widget's<span>&nbsp;</span><code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">input_schema</code>.</td></tr><tr data-line="22" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">input</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Client-supplied payload, from<span>&nbsp;</span><code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">this.server.get({action, data})</code>. Empty on the initial render.</td></tr></tbody></table>
<!--kg-card-end: html-->
<p>Plus everything the platform exposes server-side:</p>
<!--kg-card-begin: html-->
<table data-line="26" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="26" class="code-line" dir="auto" style="position: relative;"><tr data-line="26" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Global</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="28" class="code-line" dir="auto" style="position: relative;"><tr data-line="28" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">$aiux</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">The AIUX scriptable. New to this framework — see below.</td></tr><tr data-line="29" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">gs</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Standard<span>&nbsp;</span><code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">GlideSystem</code>.</td></tr><tr data-line="30" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">GlideRecord, GlideRecordSecure, GlideQuery, GlideAggregate, GlideDateTime, GlideAjax, GlideSysAttachment, GlideStringUtil, …</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Standard server APIs. Same behavior as anywhere else on the platform.</td></tr></tbody></table>
<!--kg-card-end: html-->
<hr><h2 id="aiux-%E2%80%94-the-aiux-scriptable"><code>$aiux</code>&nbsp;— the AIUX scriptable</h2><p>The widget server-script context. A pure-JavaScript scriptable, not a Rhino-bridged Java object —&nbsp;<code>getClass()</code>&nbsp;will fail,&nbsp;<code>for...in</code>&nbsp;returns nothing. Don't try to introspect it; the surface is enumerated here.</p><p>Four documented methods:</p>
<!--kg-card-begin: html-->
<table data-line="40" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="40" class="code-line" dir="auto" style="position: relative;"><tr data-line="40" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Method</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Returns</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="42" class="code-line" dir="auto" style="position: relative;"><tr data-line="42" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">$aiux.getParameter(name)</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">string|null</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Walks precedence:<span>&nbsp;</span><strong>experience → page → path → search → request</strong>. The "give me whatever's there" lookup.</td></tr><tr data-line="43" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">$aiux.getPathParameter(name)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string|null</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">URL path parameter only (e.g.<span>&nbsp;</span><code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">:sys_id</code><span>&nbsp;</span>in<span>&nbsp;</span><code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">/record/:table/:sys_id</code>).</td></tr><tr data-line="44" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">$aiux.getSearchParameter(name)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string|null</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">URL query-string parameter only.</td></tr><tr data-line="45" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">$aiux.getWidget(widgetId, options?)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">{properties, tagName}</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Fetch another widget's fully-populated server data. The composition primitive — Service Portal's<span>&nbsp;</span><code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">$sp.getWidget</code><span>&nbsp;</span>analog.</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="choosing-between-the-three-getters">Choosing between the three getters</h3><p><code>getParameter</code>&nbsp;is the omnibus call — it walks&nbsp;<code>experience → page → path → search → request</code>&nbsp;and returns the first hit. Use it when you don't care which surface the value came from.</p><p><code>getPathParameter</code>&nbsp;and&nbsp;<code>getSearchParameter</code>&nbsp;are the precise versions for when you do care. Example:&nbsp;<code>sys_id</code>&nbsp;could appear in both&nbsp;<code>/record/incident/:sys_id</code>&nbsp;and&nbsp;<code>?sys_id=...</code>. If you need to distinguish, call the specific getter directly.</p><pre><code class="language-js">data.sys_id  = $aiux.getParameter('sys_id');           // first wins
data.path_id = $aiux.getPathParameter('sys_id');       // path only
data.query_id = $aiux.getSearchParameter('sys_id');    // query only
</code></pre><h3 id="aiuxgetwidget-%E2%80%94-composition"><code>$aiux.getWidget</code>&nbsp;— composition</h3><p><code>$aiux.getWidget</code>&nbsp;is the spiritual successor to Service Portal's&nbsp;<code>$sp.getWidget</code>. Calling it server-side returns the&nbsp;<em>fully-populated</em>&nbsp;<code>data</code>&nbsp;and&nbsp;<code>properties</code>&nbsp;of another&nbsp;<code>sys_aix_widget</code>. You pass&nbsp;<code>options</code>&nbsp;(the same shape the wrapped widget's&nbsp;<code>input_schema</code>&nbsp;declares), the framework executes that widget's server script, and you get its output back.</p><pre><code class="language-js">const childData = $aiux.getWidget('people-card', {
  user_sys_id: gs.getUserID(),
});
// childData = { properties: {...}, tagName: 'people-card' }
</code></pre><p>That return value powers two patterns:</p><ul><li>Render&nbsp;<code>&lt;${childData.tagName}&gt;</code>&nbsp;in your Lit template (with&nbsp;<code>unsafeStatic</code>) to embed the other widget.</li><li>Use&nbsp;<code>childData.properties</code>&nbsp;to compose a custom UI that includes the other widget's data without rendering its template.</li></ul><p>For the broader AI composition story see&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/ai-integration/">AI Integration</a>.</p><hr><h2 id="the-glidespscriptable-sidecar-pattern">The&nbsp;<code>GlideSPScriptable</code>&nbsp;sidecar pattern</h2><p>The one piece of&nbsp;<code>gs.*</code>&nbsp;worth flagging specifically. When an AIUX widget needs a portal-scoped&nbsp;<code>$sp</code>&nbsp;context — for parameter-passing helpers, cart/catalog state, anything that historically required&nbsp;<code>new GlideSPScriptable('portal_id')</code>&nbsp;— bind it to the AIUX sidecar&nbsp;<code>sp_portal</code>&nbsp;record:</p><pre><code class="language-js">const AIUX_PORTAL_ID = "7cf6e70a3f123210860f2248001f8b63";  // sp_portal "aiuxsp"
var sp = new GlideSPScriptable(AIUX_PORTAL_ID);
</code></pre><p>The OOB Activity Stream widget does this verbatim. The sys_id is stable across instances —&nbsp;<code>aiuxsp</code>&nbsp;is the SP portal record that ships with the AIUX apps, and&nbsp;<code>GlideSPScriptable</code>&nbsp;bound to it gives you the same&nbsp;<code>$sp.*</code>&nbsp;surface you'd have inside a real Service Portal page.</p><p>This is the one place server-script code legitimately reaches into Service Portal infrastructure. See&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/experiences-overview/">Experiences → Overview</a>&nbsp;for the framework-level context.</p><hr><h2 id="server-%E2%86%92-client-data-flow">Server → client data flow</h2><p>Your server script populates&nbsp;<code>data</code>. On the client, the Lit widget reads it via&nbsp;<code>this.data</code>&nbsp;(provided automatically by&nbsp;<code>AIUXWidgetElement</code>):</p><pre><code class="language-js">// server script
(function(data, options, input) {
  data.user = { id: gs.getUserID(), name: gs.getUser().getDisplayName() };
})(data, options, input);
</code></pre><pre><code class="language-js">// component
render() {
  return html`&lt;p&gt;Hello, ${this.data?.user?.name}&lt;/p&gt;`;
}
</code></pre><p>To re-run the server script (for refresh after an action, or to fetch with new options):&nbsp;<code>this.server.refresh()</code>. To send an explicit payload:&nbsp;<code>this.server.get({ action: 'doThing', data: {...} })</code>&nbsp;— the server script receives the payload as&nbsp;<code>input</code>.</p><pre><code class="language-js">// server script handling an action
(function(data, options, input) {
  if (input &amp;&amp; input.action === 'doThing') {
    // mutate via GlideRecord, populate data with the result
  }
})(data, options, input);
</code></pre><p>For the full&nbsp;<code>this.server.*</code>&nbsp;API see&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/component/">Component → Instance members</a>.</p><hr><h2 id="whats-not-on-this-page">What's&nbsp;<em>not</em>&nbsp;on this page</h2><p>The standard server-side APIs —&nbsp;<code>gs.*</code>,&nbsp;<code>GlideRecord</code>,&nbsp;<code>GlideQuery</code>,&nbsp;<code>GlideAggregate</code>,&nbsp;<code>GlideDateTime</code>,&nbsp;<code>GlideUser</code>,&nbsp;<code>GlideSession</code>, attachments, encryption, schedules, table hierarchy, and the rest — work exactly as they do anywhere else on the platform. They're not framework-specific and they're not re-documented here.</p><p>If you need a quick lookup, the&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/intellisense/">Intellisense reference</a>&nbsp;has the full surface as a single searchable artifact, or the official ServiceNow docs are authoritative for behavior and edge cases.</p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Service Portal Bridge ]]></title>
        <description><![CDATA[ The single most important architectural decision in sn_aiux is this: it doesn&#39;t replace sp_widget. It embeds it.

Three of the nineteen out-of-the-box widgets are pure adapters around existing Angular Service Portal widgets, surfacing them inside an sn_aiux experience with no modifications to the underlying widget. ]]></description>
        <link>https://relay.semaphorepartners.com/aiux-docs/service-portal-bridge/</link>
        <guid isPermaLink="false">6a033def2c447200013e12a9</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Tue, 12 May 2026 10:49:42 -0400</pubDate>
        <media:content url="" medium="image"/>
        <content:encoded><![CDATA[ <p>The single most important architectural decision in&nbsp;<code>sn_aiux</code>&nbsp;is this:&nbsp;<strong>it doesn't replace&nbsp;<code>sp_widget</code>. It embeds it.</strong></p><p>Three of the nineteen out-of-the-box widgets are pure adapters around existing Angular Service Portal widgets, surfacing them inside an&nbsp;<code>sn_aiux</code>&nbsp;experience with no modifications to the underlying widget. The mechanism is a Lit custom element called&nbsp;<code>&lt;aiux-angular-element&gt;</code>, and the pattern it enables is the migration story for anything you've already built.</p><h2 id="aiux-angular-element"><code>&lt;aiux-angular-element&gt;</code></h2><p>A Lit element that, at mount time, bootstraps the embedded AngularJS Service Portal engine inside the Lit DOM, looks up an&nbsp;<code>sp_widget</code>&nbsp;by id, runs that widget's server script with the supplied&nbsp;<code>options</code>, and renders its Angular template inside the&nbsp;<code>sn_aiux</code>&nbsp;shell.</p><p>Usage from inside a&nbsp;<code>sys_aix_widget</code>&nbsp;Lit component:</p><pre><code class="language-js">render() {
  return html`
    &lt;aiux-angular-element
      .widgetId=${'widget-form'}
      .options=${this.options}&gt;
    &lt;/aiux-angular-element&gt;`;
}
</code></pre><p>Two property bindings, both with the&nbsp;<code>.</code>&nbsp;prefix (Lit JS bindings, not HTML attributes):</p>
<!--kg-card-begin: html-->
<table data-line="24" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="24" class="code-line" dir="auto" style="position: relative;"><tr data-line="24" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Property</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Type</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="26" class="code-line" dir="auto" style="position: relative;"><tr data-line="26" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">.widgetId</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">The<span>&nbsp;</span><code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">sp_widget.id</code><span>&nbsp;</span>(kebab-case slug) of the widget to embed.</td></tr><tr data-line="27" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">.options</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Object</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Options to pass to the wrapped widget. Same shape its<span>&nbsp;</span><code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">option_schema</code><span>&nbsp;</span>declares.</td></tr></tbody></table>
<!--kg-card-end: html-->
<h2 id="how-it-works-at-runtime">How it works at runtime</h2><p>When the Lit component mounts&nbsp;<code>&lt;aiux-angular-element&gt;</code>:</p><ol><li>The framework spins up the embedded AngularJS Service Portal runtime inside the Lit DOM tree.</li><li>It looks up the&nbsp;<code>sp_widget</code>&nbsp;record by&nbsp;<code>id</code>.</li><li>It runs that widget's server&nbsp;<code>script</code>&nbsp;with&nbsp;<code>options</code>&nbsp;populated from&nbsp;<code>.options</code>.</li><li>It renders the widget's Angular template + client_script inside the shell.</li></ol><p>The wrapped widget runs exactly as it does in Service Portal.&nbsp;<code>$sp</code>&nbsp;parameter passing works.&nbsp;<code>gs.*</code>&nbsp;and&nbsp;<code>GlideRecord</code>&nbsp;work. The widget's&nbsp;<code>data</code>&nbsp;object is populated by its own server script.</p><h2 id="the-three-oob-bridge-widgets">The three OOB bridge widgets</h2>
<!--kg-card-begin: html-->
<table data-line="42" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="42" class="code-line" dir="auto" style="position: relative;"><tr data-line="42" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);"><code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">sn_aiux</code><span>&nbsp;</span>widget</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Embeds<span>&nbsp;</span><code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">sp_widget</code></th></tr></thead><tbody data-line="44" class="code-line" dir="auto" style="position: relative;"><tr data-line="44" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Form (<code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">form-widget</code>)</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);"><code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">widget-form</code></td></tr><tr data-line="45" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Catalog Item (<code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">catalog-item</code>)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);"><code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">widget-sc-cat-item-v2</code></td></tr><tr data-line="46" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Order Guide (<code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">order-guide</code>)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);"><code style="font-family: Menlo, Monaco, &quot;Courier New&quot;, monospace; color: rgb(96, 96, 96); background-color: rgb(236, 236, 236); padding: 1px 3px; border-radius: 4px; font-size: 1em; line-height: 1.357em;">widget-sc-order-guide-v2</code></td></tr></tbody></table>
<!--kg-card-end: html-->
<p>Here's the entire&nbsp;<code>component</code>&nbsp;for the Form widget — a complete, working bridge in twenty lines:</p><pre><code class="language-js">import { html } from 'lit';
import { AIUXWidgetElement } from '@servicenow/aiux-components-core';
import { locationService } from '@servicenow/aiux-services';

class FormWidget extends AIUXWidgetElement {
  createRenderRoot() { return this; }
  constructor() {
    super();
    const params = locationService.params();
    this.options = {
      table: params.table,
      sys_id: params.sys_id,
      hideRelatedLists: true,
    };
  }
  render() {
    return html`
      &lt;aiux-angular-element
        .widgetId=${'widget-form'}
        .options=${this.options}&gt;
      &lt;/aiux-angular-element&gt;`;
  }
}
</code></pre><p>The Form widget's own server script is empty — there's nothing for it to do. All the work happens inside&nbsp;<code>widget-form</code>.</p><h2 id="when-to-use-the-bridge">When to use the bridge</h2><p>Use the bridge when:</p><ul><li>A complex Angular widget already exists and works. Catalog items, order guides, record forms, anything with cart logic, variable trees, or form-engine bindings.</li><li>You want to ship an&nbsp;<code>sn_aiux</code>&nbsp;experience quickly without rewriting your widget library.</li><li>You're migrating an existing Service Portal portal to&nbsp;<code>sn_aiux</code>&nbsp;and want the cutover to be invisible to end users.</li></ul><p>Use native Lit when:</p><ul><li>You're building a new widget from scratch.</li><li>The widget needs&nbsp;<code>client_tools</code>&nbsp;for AI agent integration (the bridge layer can't expose those on the embedded widget).</li><li>The widget is presentational and doesn't need the Service Portal scriptable runtime.</li></ul><p>The two coexist inside the same page. A typical real-world experience mixes native Lit widgets with bridged Service Portal widgets without anyone noticing the seam.</p><h2 id="the-theming-sidecar">The theming sidecar</h2><p>When you bridge a Service Portal widget into&nbsp;<code>sn_aiux</code>, the embedded Angular renderer needs a theme to style the widget with. That's where the&nbsp;<code>sp_portal</code>&nbsp;record with url_suffix&nbsp;<code>aiuxsp</code>&nbsp;comes in — it's a sidecar that carries the theme used to reskin embedded SP widgets so they render against Tailwind/DaisyUI tokens instead of looking like 2016-era Bootstrap dropped into a modern page.</p><p><code>sn_aiux</code>&nbsp;itself does not run on Service Portal. The&nbsp;<code>aiuxsp</code>&nbsp;portal record only matters when SP widgets get bridged in. Pure-Lit&nbsp;<code>sn_aiux</code>&nbsp;widgets never touch it. See&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/experiences-overview/">Experiences → Overview</a>&nbsp;for the framework-level context.</p><h2 id="worked-example">Worked example</h2><p>For a step-by-step walkthrough that takes the OOB Cool Clock Service Portal widget and surfaces it inside an&nbsp;<code>sn_aiux</code>&nbsp;page, see&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/your-first-widget/">Getting Started → Your first widget</a>.</p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Daisy ]]></title>
        <description><![CDATA[ sn_aiux ships with a styling system based on Tailwind CSS and DaisyUI, bundled and prefixed under aiux-. You have two distinct surfaces to work with: pre-built &lt;aiux-*&gt; web components for high-level pieces (dialog, alert, accordion, etc.), and Tailwind/DaisyUI utility classes (e.g. aiux-card, aiux-table-zebra, aiux-badge-primary) for ]]></description>
        <link>https://relay.semaphorepartners.com/aiux-docs/daisy/</link>
        <guid isPermaLink="false">6a033d812c447200013e1294</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Tue, 12 May 2026 10:47:57 -0400</pubDate>
        <media:content url="" medium="image"/>
        <content:encoded><![CDATA[ <p>sn_aiux ships with a styling system based on&nbsp;<strong>Tailwind CSS</strong>&nbsp;and&nbsp;<strong>DaisyUI</strong>, bundled and prefixed under&nbsp;<code>aiux-</code>. You have two distinct surfaces to work with: pre-built&nbsp;<code>&lt;aiux-*&gt;</code>&nbsp;web components for high-level pieces (dialog, alert, accordion, etc.), and Tailwind/DaisyUI utility classes (e.g.&nbsp;<code>aiux-card</code>,&nbsp;<code>aiux-table-zebra</code>,&nbsp;<code>aiux-badge-primary</code>) for everything else.</p><p>Pick whichever fits the task. The web components give you semantics, accessibility, slotting, and configurable behavior in one tag. The utility classes give you fine-grained composition without writing CSS. Most real widgets use both.</p><hr><h2 id="pre-built-aiuxcomponents">Pre-built&nbsp;<code>&lt;aiux-*&gt;</code>&nbsp;components</h2><p>All registered globally — just use the tag. Where a side-effect import is needed, it looks like&nbsp;<code>import '@servicenow/aiux-components-core/aiux-alert-message'</code>.</p><p>Attribute vs property: lowercase kebab-case names like&nbsp;<code>is-modal</code>&nbsp;are HTML attributes. Names prefixed with&nbsp;<code>.</code>&nbsp;(e.g.&nbsp;<code>.items</code>) are Lit JS property bindings — used when the value is a non-string (an array, object, etc.).</p><h3 id="aiux-accordion-group"><code>&lt;aiux-accordion-group&gt;</code></h3><p>Collapsible sections with&nbsp;<code>arrow</code>&nbsp;or&nbsp;<code>plus</code>&nbsp;icons.</p>
<!--kg-card-begin: html-->
<table data-line="18" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="18" class="code-line" dir="auto" style="position: relative;"><tr data-line="18" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Attr/Property</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Type</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="20" class="code-line" dir="auto" style="position: relative;"><tr data-line="20" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">.items</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Array</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">{title, content, open?}</td></tr><tr data-line="21" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">multiple</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Allow multiple open items</td></tr><tr data-line="22" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">icon</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">"arrow" | "plus"</td></tr><tr data-line="23" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">bordered</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Show item borders</td></tr><tr data-line="24" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">onchange</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Callback code; detail {index, title, open}</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="aiux-alert-message"><code>&lt;aiux-alert-message&gt;</code></h3><p>Inline alert / banner.</p>
<!--kg-card-begin: html-->
<table data-line="30" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="30" class="code-line" dir="auto" style="position: relative;"><tr data-line="30" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Attr/Property</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Type</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="32" class="code-line" dir="auto" style="position: relative;"><tr data-line="32" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">type</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">"info" | "success" | "warning" | "error" | "primary" | "accent"</td></tr><tr data-line="33" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">message</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Body text</td></tr><tr data-line="34" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">alert-title</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Optional title</td></tr><tr data-line="35" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">dismissible</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Show dismiss button</td></tr><tr data-line="36" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">icon</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Custom icon name (overrides default)</td></tr><tr data-line="37" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">duration</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">number</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Auto-dismiss ms (0 = none)</td></tr><tr data-line="38" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">autofocus-controls</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Autofocus dismiss button</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="aiux-calendar-component"><code>&lt;aiux-calendar-component&gt;</code></h3><p>Month-view calendar with selectable date.</p>
<!--kg-card-begin: html-->
<table data-line="44" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="44" class="code-line" dir="auto" style="position: relative;"><tr data-line="44" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Attr/Property</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Type</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="46" class="code-line" dir="auto" style="position: relative;"><tr data-line="46" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">.currentDate</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Object</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Displayed month {month, year}</td></tr><tr data-line="47" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">.selectedDate</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Object</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Selected date</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="aiux-carousel-slider"><code>&lt;aiux-carousel-slider&gt;</code></h3><p>Slideshow with optional autoplay, dot indicators, and navigation buttons.</p>
<!--kg-card-begin: html-->
<table data-line="53" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="53" class="code-line" dir="auto" style="position: relative;"><tr data-line="53" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Attr/Property</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Type</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="55" class="code-line" dir="auto" style="position: relative;"><tr data-line="55" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">.items</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Array</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Slide content items</td></tr><tr data-line="56" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">show-navigation</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Prev/next buttons</td></tr><tr data-line="57" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">show-indicators</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Dot indicators</td></tr><tr data-line="58" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">autoplay</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">number</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Autoplay interval ms (0 = off)</td></tr><tr data-line="59" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">alignment</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Slide alignment</td></tr><tr data-line="60" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">vertical</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Vertical orientation</td></tr><tr data-line="61" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">onchange</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Callback; detail {index, previousIndex}</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="aiux-dialog"><code>&lt;aiux-dialog&gt;</code></h3><p>Modal or non-modal dialog with header, body, and footer slots.</p>
<!--kg-card-begin: html-->
<table data-line="67" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="67" class="code-line" dir="auto" style="position: relative;"><tr data-line="67" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Attr/Property</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Type</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="69" class="code-line" dir="auto" style="position: relative;"><tr data-line="69" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">open</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Dialog is open</td></tr><tr data-line="70" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">size</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">"sm" | "md" | "lg" | "xl" | "fullscreen"</td></tr><tr data-line="71" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">is-modal</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Render as modal</td></tr><tr data-line="72" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">show-close-button</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Show close button</td></tr><tr data-line="73" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">close-on-escape</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Close on Escape</td></tr><tr data-line="74" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">close-on-backdrop-click</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Close on backdrop click</td></tr><tr data-line="75" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">prevent-scroll</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Prevent body scroll when modal open</td></tr><tr data-line="76" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">resizable</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Drag-to-resize</td></tr><tr data-line="77" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">hide-padding</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Remove header/body/footer padding</td></tr><tr data-line="78" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">dialog-label</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">aria-label</td></tr><tr data-line="79" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">.customSize</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Object</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">{width, height}</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="aiux-icon"><code>&lt;aiux-icon&gt;</code></h3><p>Inline icon rendered from the framework's icon library.</p>
<!--kg-card-begin: html-->
<table data-line="85" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="85" class="code-line" dir="auto" style="position: relative;"><tr data-line="85" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Attr/Property</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Type</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="87" class="code-line" dir="auto" style="position: relative;"><tr data-line="87" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">name</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Icon name in kebab-case (e.g. "play-fill")</td></tr><tr data-line="88" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">size</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">"xs" | "sm" | "md" | "lg" | "xl"</td></tr><tr data-line="89" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">.configAria</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Object</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">{role, label}</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="aiux-live-region"><code>&lt;aiux-live-region&gt;</code></h3><p>ARIA live region for accessibility announcements.</p>
<!--kg-card-begin: html-->
<table data-line="95" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="95" class="code-line" dir="auto" style="position: relative;"><tr data-line="95" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Attr/Property</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Type</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="97" class="code-line" dir="auto" style="position: relative;"><tr data-line="97" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">mode</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">"polite" | "assertive" | "off"</td></tr><tr data-line="98" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">atomic</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">aria-atomic</td></tr><tr data-line="99" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">relevant</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">aria-relevant</td></tr><tr data-line="100" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">visually-hidden</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Invisible but still announced</td></tr><tr data-line="101" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">region-role</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Override role</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="aiux-loader"><code>&lt;aiux-loader&gt;</code></h3><p>Spinner / loading indicator.</p>
<!--kg-card-begin: html-->
<table data-line="107" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="107" class="code-line" dir="auto" style="position: relative;"><tr data-line="107" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Attr/Property</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Type</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="109" class="code-line" dir="auto" style="position: relative;"><tr data-line="109" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">size</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">"xs" | "sm" | "md" | "lg" | "xl"</td></tr><tr data-line="110" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">label</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Accessible loading label</td></tr><tr data-line="111" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">.animationConfig</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Object</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Lottie config</td></tr><tr data-line="112" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">show-footer</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Show footer logo</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="aiux-pagination-nav"><code>&lt;aiux-pagination-nav&gt;</code></h3><p>Page-number navigation control.</p>
<!--kg-card-begin: html-->
<table data-line="118" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="118" class="code-line" dir="auto" style="position: relative;"><tr data-line="118" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Attr/Property</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Type</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="120" class="code-line" dir="auto" style="position: relative;"><tr data-line="120" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">current-page</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">number</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Active page (1-based)</td></tr><tr data-line="121" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">total-pages</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">number</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Total pages</td></tr><tr data-line="122" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">max-buttons</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">number</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Max page buttons</td></tr><tr data-line="123" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">show-prev-next</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Prev/next arrows</td></tr><tr data-line="124" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">show-first-last</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">First/last arrows</td></tr><tr data-line="125" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">size</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">"xs" | "sm" | "md" | "lg" | "xl"</td></tr><tr data-line="126" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">onchange</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Callback; detail {page, previousPage, totalPages}</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="aiux-tab-group"><code>&lt;aiux-tab-group&gt;</code></h3><p>Tabbed sections.</p>
<!--kg-card-begin: html-->
<table data-line="132" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="132" class="code-line" dir="auto" style="position: relative;"><tr data-line="132" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Attr/Property</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Type</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="134" class="code-line" dir="auto" style="position: relative;"><tr data-line="134" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">.tabs</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Array</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">{label, id, content?, disabled?}</td></tr><tr data-line="135" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">active-tab</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Active tab id</td></tr><tr data-line="136" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">variant</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">"boxed" | "bordered" | "lifted"</td></tr><tr data-line="137" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">placement</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">"top" | "bottom"</td></tr><tr data-line="138" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">size</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">"xs" | "sm" | "md" | "lg" | "xl"</td></tr><tr data-line="139" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">name</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Group name</td></tr><tr data-line="140" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">stretch</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Stretch to fill width</td></tr><tr data-line="141" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">onchange</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Callback; detail {id, label, index}</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="aiux-toast-notification"><code>&lt;aiux-toast-notification&gt;</code></h3><p>Stand-alone toast (the&nbsp;<code>notifications</code>&nbsp;service produces these — see&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/component/#notifications--toast-notifications">Component → Services</a>).</p>
<!--kg-card-begin: html-->
<table data-line="147" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="147" class="code-line" dir="auto" style="position: relative;"><tr data-line="147" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Attr/Property</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Type</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="149" class="code-line" dir="auto" style="position: relative;"><tr data-line="149" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">type</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">"info" | "success" | "warning" | "error"</td></tr><tr data-line="150" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">message</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Body text</td></tr><tr data-line="151" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">position</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Screen position</td></tr><tr data-line="152" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">dismissible</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Show dismiss button</td></tr><tr data-line="153" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">icon</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Custom icon name</td></tr><tr data-line="154" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">duration</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">number</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Auto-dismiss ms</td></tr><tr data-line="155" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">visible</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Show/hide</td></tr><tr data-line="156" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">ondismiss</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Callback on dismiss</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="aiux-time-selector"><code>&lt;aiux-time-selector&gt;</code></h3><p>12/24-hour time input.</p>
<!--kg-card-begin: html-->
<table data-line="162" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="162" class="code-line" dir="auto" style="position: relative;"><tr data-line="162" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Attr/Property</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Type</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="164" class="code-line" dir="auto" style="position: relative;"><tr data-line="164" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">is-12-hour</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">12-hour format with AM/PM toggle (default true)</td></tr><tr data-line="165" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">value</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Initial time in HH:MM (24-hour)</td></tr><tr data-line="166" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">disabled</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Disable all inputs</td></tr><tr data-line="167" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">size</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">"sm" | "md" | "lg"</td></tr></tbody></table>
<!--kg-card-end: html-->
<hr><h2 id="daisyui-utility-classes">DaisyUI utility classes</h2><p>Tailwind plus DaisyUI is bundled with sn_aiux, with every class prefixed&nbsp;<code>aiux-</code>. Common ones you'll reach for:</p><ul><li><strong>Cards:</strong>&nbsp;<code>aiux-card</code>,&nbsp;<code>aiux-card-body</code>,&nbsp;<code>aiux-card-title</code>,&nbsp;<code>aiux-card-bordered</code>,&nbsp;<code>aiux-shadow-md</code></li><li><strong>Tables:</strong>&nbsp;<code>aiux-table</code>,&nbsp;<code>aiux-table-zebra</code>,&nbsp;<code>aiux-table-sm</code></li><li><strong>Badges:</strong>&nbsp;<code>aiux-badge</code>,&nbsp;<code>aiux-badge-primary</code>,&nbsp;<code>aiux-badge-outline</code>,&nbsp;<code>aiux-badge-ghost</code></li><li><strong>Buttons:</strong>&nbsp;<code>aiux-btn</code>,&nbsp;<code>aiux-btn-primary</code>,&nbsp;<code>aiux-btn-dash</code>,&nbsp;<code>aiux-btn-ghost</code></li><li><strong>Alerts (utility variant):</strong>&nbsp;<code>aiux-alert</code>,&nbsp;<code>aiux-alert-info</code>,&nbsp;<code>aiux-alert-error</code></li><li><strong>Modals (utility variant):</strong>&nbsp;<code>aiux-modal</code>,&nbsp;<code>aiux-modal-open</code>,&nbsp;<code>aiux-modal-toggle</code></li><li><strong>Layout:</strong>&nbsp;<code>aiux-bg-base-100</code>,&nbsp;<code>aiux-bg-base-200</code>,&nbsp;<code>aiux-divider</code>,&nbsp;<code>aiux-overflow-x-auto</code></li><li><strong>Typography:</strong>&nbsp;<code>aiux-text-sm</code>,&nbsp;<code>aiux-text-xs</code>,&nbsp;<code>aiux-opacity-70</code>,&nbsp;<code>aiux-opacity-60</code></li><li><strong>Spacing:</strong>&nbsp;<code>aiux-p-3</code>,&nbsp;<code>aiux-rounded</code></li></ul><p>When in doubt, search the bundled CSS file (<code>/sncapps/aix/assets/aiux-kit-*.css</code>) for the class name you want — every DaisyUI utility ships with the&nbsp;<code>aiux-</code>&nbsp;prefix applied.</p><h3 id="picking-between-a-component-and-a-utility">Picking between a component and a utility</h3><ul><li>Use&nbsp;<code><strong>&lt;aiux-alert-message&gt;</strong></code>&nbsp;when you want a configurable, accessible, dismissible alert with a typed&nbsp;<code>type</code>&nbsp;attribute.</li><li>Use&nbsp;<strong><code>aiux-alert aiux-alert-info</code></strong>&nbsp;classes when you just need a styled box around your own content.</li><li>Use&nbsp;<strong><code>&lt;aiux-dialog&gt;</code></strong>&nbsp;when you want a real modal with focus trapping, escape handling, and a backdrop.</li><li>Use&nbsp;<strong><code>aiux-modal</code></strong>&nbsp;utility classes when you want to style a custom overlay that doesn't behave like a true modal.</li></ul><p>The pattern generalizes: pre-built component for behavior + accessibility, utility classes for composition.</p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ AI Integration ]]></title>
        <description><![CDATA[ sn_aiux is the first ServiceNow ux framework where widgets are first-class participants in conversational AI flows. Two widget-record fields and two runtime APIs do most of the work:

 * best_for — natural-language hint the AI agent reads to decide when to invoke the widget.
 * client_tools — JSON manifest of UI ]]></description>
        <link>https://relay.semaphorepartners.com/aiux-docs/ai-integration/</link>
        <guid isPermaLink="false">6a033cdd2c447200013e1280</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Tue, 12 May 2026 10:46:54 -0400</pubDate>
        <media:content url="" medium="image"/>
        <content:encoded><![CDATA[ <p><code>sn_aiux</code>&nbsp;is the first ServiceNow ux framework where widgets are first-class participants in conversational AI flows. Two widget-record fields and two runtime APIs do most of the work:</p><ul><li><code>best_for</code>&nbsp;— natural-language hint the AI agent reads to decide&nbsp;<em>when</em>&nbsp;to invoke the widget.</li><li><code>client_tools</code>&nbsp;— JSON manifest of UI actions the widget exposes to the agent at runtime.</li><li><code>this.aiContext</code>&nbsp;/&nbsp;<code>setAiContext(ctx)</code>&nbsp;— per-instance AI context the widget can write into.</li><li><code>$aiux.getWidget(widgetId, options?)</code>&nbsp;— server-side composition primitive that lets one widget pull another widget's fully-populated data, enabling widget orchestration.</li></ul><p>This page covers each in turn.</p><h2 id="bestfor"><code>best_for</code></h2><p>A field on the&nbsp;<code>sys_aix_widget</code>&nbsp;record. It's a free-text description engineered for the agent's decision-making, not the developer's — written so an LLM reading the field list can match a user's intent to the right widget.</p><p>Example, from the OOB Activity Stream widget:</p><blockquote><em>Displaying ticket or record activity history with comments, work notes, and attachments in a chronological timeline. Ideal for case management, incident tracking, and any record that needs a conversation-style activity feed with the ability to post new entries.</em></blockquote><p>Write&nbsp;<code>best_for</code>&nbsp;the way you'd write a tool description for an agent prompt: be concrete about what the widget shows, what kind of records it works on, and when an agent should reach for it instead of something else.</p><h2 id="clienttools"><code>client_tools</code></h2><p>A JSON field on the widget record that declares a set of UI actions the widget exposes to AI agents. Each tool has a&nbsp;<code>description</code>&nbsp;(read by the LLM to decide whether to call it), an&nbsp;<code>arguments</code>&nbsp;schema, and a JS handler bound to the widget instance.</p><p>Example, from Activity Stream:</p><pre><code class="language-json">{
  "postComment": {
    "description": "[IMMEDIATE ACTION - UI CONTROL] Posts a comment to the activity stream. EXECUTE THIS TOOL when user wants to add a comment, note, or message to the ticket/record. This posts to the journal field (comments or work notes depending on configuration). Keywords: comment, post, add note, reply, respond, message.",
    "arguments": [
      { "name": "input", "type": "string", "description": "The comment text to post to the activity stream", "required": true }
    ]
  }
}
</code></pre><p>The widget's Lit class registers a matching&nbsp;<code>static client_tools</code>&nbsp;entry so the framework can dispatch the call into a real JS method when the agent fires it:</p><pre><code class="language-js">class ActivityStream extends AIUXWidgetElement {
  static client_tools = {
    postComment: {
      definition: ActivityStream.prototype.post,
      description: '[IMMEDIATE ACTION - UI CONTROL] Posts a comment to the activity stream. EXECUTE THIS TOOL when user wants to add a comment, note, or message to the ticket/record.',
      arguments: [
        { name: 'input', type: 'string', description: 'The comment text to post', required: true }
      ]
    }
  };
}
</code></pre><h3 id="description-conventions">Description conventions</h3><p>OOB widgets share a few writing patterns worth copying:</p><ul><li><strong><code>[IMMEDIATE ACTION - UI CONTROL]</code></strong>&nbsp;prefix when the agent should call the tool eagerly, without confirmation.</li><li><strong>Keyword list</strong>&nbsp;at the end (<code>Keywords: comment, post, add note, reply, respond, message.</code>) — explicit triggers the LLM can match against user phrasing.</li><li><strong>EXECUTE THIS TOOL when ...</strong>&nbsp;— capitalized, imperative, action-trigger phrasing.</li></ul><h3 id="what-oob-widgets-expose">What OOB widgets expose</h3><p>A non-exhaustive tour of&nbsp;<code>client_tools</code>&nbsp;declarations across the OOB library:</p>
<!--kg-card-begin: html-->
<table data-line="66" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="66" class="code-line" dir="auto" style="position: relative;"><tr data-line="66" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Widget</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Tools</th></tr></thead><tbody data-line="68" class="code-line" dir="auto" style="position: relative;"><tr data-line="68" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Activity Stream</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">postComment</td></tr><tr data-line="69" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Add attachments</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">parameters, properties</td></tr><tr data-line="70" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Breakout Game</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">reset-game, pause-game, get-game-state, set-difficulty</td></tr><tr data-line="71" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Employee Profile Card</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">loadProfileDetails</td></tr><tr data-line="72" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">People Card</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">showOrgChart, refresh</td></tr></tbody></table>
<!--kg-card-end: html-->
<p>The Breakout Game is a deliberate teaching example — four tools that map cleanly to natural user requests ("reset the game", "pause the game", "what's the score?", "make it harder") and the implementation is small enough to read in one sitting.</p><h2 id="thisaicontext-and-setaicontextctx"><code>this.aiContext</code>&nbsp;and&nbsp;<code>setAiContext(ctx)</code></h2><p>Each widget instance gets an&nbsp;<code>aiContext</code>&nbsp;slot the framework reads when constructing agent prompts. Use&nbsp;<code>this.setAiContext(ctx)</code>&nbsp;to write a structured object describing the widget's current state.</p><pre><code class="language-js">firstUpdated() {
  this.setAiContext({
    record: { table: 'incident', sys_id: this.sysId },
    state: this.data?.state,
    assignee: this.data?.assignee?.display_value,
  });
}
</code></pre><p>When the agent has access to the widget, that context is part of the prompt — letting the LLM make better decisions about which&nbsp;<code>client_tools</code>&nbsp;to invoke and with what arguments. Update the context whenever the widget's state changes.</p><h2 id="aiuxgetwidget-%E2%80%94-server-side-composition"><code>$aiux.getWidget</code>&nbsp;— server-side composition</h2><p>The composition primitive that Service Portal had as&nbsp;<code>$sp.getWidget</code>, preserved and renamed for the new framework. Inside a widget's server script:</p><pre><code class="language-js">const childData = $aiux.getWidget('people-card', {
  user_sys_id: gs.getUserID(),
});
// childData = { properties: {...}, tagName: 'people-card' }
</code></pre><p><code>$aiux.getWidget</code>&nbsp;executes another widget's server script with the supplied options and returns its&nbsp;<code>data</code>&nbsp;and rendered&nbsp;<code>properties</code>. You can then either:</p><ul><li>Render the returned&nbsp;<code>tagName</code>&nbsp;directly in your Lit template, or</li><li>Use the returned&nbsp;<code>properties</code>&nbsp;to compose a custom UI that includes the other widget's data without rendering its template.</li></ul><p>This is how the AIUX equivalent of&nbsp;<code>&lt;sp-widget&gt;</code>&nbsp;works under the hood. It also lets agentic widgets orchestrate other widgets server-side without round-tripping through the client.</p><p></p><p></p><p></p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Lit ]]></title>
        <description><![CDATA[ sn_aiux widgets are Lit web components — specifically, classes that extend AIUXWidgetElement (which itself extends LitElement). This page covers the raw Lit surface that any widget author needs: the html and css tagged templates, the standard directives, and the ContextConsumer integration. For the AIUX-specific additions (services, lifecycle extras, client_tools, ]]></description>
        <link>https://relay.semaphorepartners.com/aiux-docs/lit/</link>
        <guid isPermaLink="false">6a033c632c447200013e126b</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Tue, 12 May 2026 10:43:06 -0400</pubDate>
        <media:content url="" medium="image"/>
        <content:encoded><![CDATA[ <p>sn_aiux widgets are Lit web components — specifically, classes that extend&nbsp;<code>AIUXWidgetElement</code>&nbsp;(which itself extends&nbsp;<code>LitElement</code>). This page covers the raw Lit surface that any widget author needs: the&nbsp;<code>html</code>&nbsp;and&nbsp;<code>css</code>&nbsp;tagged templates, the standard directives, and the&nbsp;<code>ContextConsumer</code>&nbsp;integration. For the AIUX-specific additions (services, lifecycle extras,&nbsp;<code>client_tools</code>, the framework's own directives) see&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/component/">Component</a>.</p><p>If you've never written Lit before, the&nbsp;<a href="https://lit.dev/?ref=relay.semaphorepartners.com">official documentation</a>&nbsp;is the canonical reference. The cheat sheet here is just the surface that ships in the sn_aiux bundle, with the import paths you'll actually need inside a widget.</p><hr><h2 id="templating-html-css-svg">Templating:&nbsp;<code>html</code>,&nbsp;<code>css</code>,&nbsp;<code>svg</code></h2><p><code>import { html, css, svg, nothing } from 'lit'</code></p>
<!--kg-card-begin: html-->
<table data-line="12" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="12" class="code-line" dir="auto" style="position: relative;"><tr data-line="12" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Symbol</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Kind</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Signature</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Returns</th></tr></thead><tbody data-line="14" class="code-line" dir="auto" style="position: relative;"><tr data-line="14" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">html</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">function</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">html...</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">TemplateResult</td></tr><tr data-line="15" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">css</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">function</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">css...</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">CSSResult</td></tr><tr data-line="16" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">svg</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">function</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">svg...</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">SVGTemplateResult</td></tr><tr data-line="17" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">nothing</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">constant</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">—</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Sentinel for "render nothing"</td></tr></tbody></table>
<!--kg-card-end: html-->
<p>Use&nbsp;<code>html</code>&nbsp;inside&nbsp;<code>render()</code>,&nbsp;<code>css</code>&nbsp;inside&nbsp;<code>static styles</code>, and&nbsp;<code>nothing</code>&nbsp;as a conditional-render escape hatch:</p><pre><code class="language-js">render() {
  return html`
    &lt;h2&gt;${this.title}&lt;/h2&gt;
    ${this.showDetails ? html`&lt;p&gt;${this.body}&lt;/p&gt;` : nothing}
  `;
}
</code></pre><hr><h2 id="standard-directives">Standard directives</h2><p>Each directive is imported from its own submodule. All return&nbsp;<code>DirectiveResult</code>&nbsp;and are interpolated into an&nbsp;<code>html</code>&nbsp;template the same way as values.</p>
<!--kg-card-begin: html-->
<table data-line="36" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="36" class="code-line" dir="auto" style="position: relative;"><tr data-line="36" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Directive</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Import</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Signature</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="38" class="code-line" dir="auto" style="position: relative;"><tr data-line="38" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">repeat</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">lit/directives/repeat.js</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">repeat(items, keyFn, template)</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Keyed list rendering. keyFn returns an identity key per item.</td></tr><tr data-line="39" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">classMap</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">lit/directives/class-map.js</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">classMap(classInfo)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Toggle classes from an object of {className: boolean}.</td></tr><tr data-line="40" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">styleMap</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">lit/directives/style-map.js</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">styleMap(styleInfo)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Inline styles from an object of {cssProp: value}.</td></tr><tr data-line="41" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">ifDefined</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">lit/directives/if-defined.js</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">ifDefined(value)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Removes the attribute if value is undefined.</td></tr><tr data-line="42" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">guard</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">lit/directives/guard.js</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">guard(deps, valueFn)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Re-evaluate valueFn only when one of deps changes.</td></tr><tr data-line="43" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">until</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">lit/directives/until.js</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">until(...values)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Render promises in resolution order; last value wins.</td></tr><tr data-line="44" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">unsafeHTML</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">lit/directives/unsafe-html.js</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">unsafeHTML(value)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Inject raw HTML. Sanitize first.</td></tr></tbody></table>
<!--kg-card-end: html-->
<p>Example combining&nbsp;<code>repeat</code>&nbsp;and&nbsp;<code>classMap</code>:</p><pre><code class="language-js">import { repeat } from 'lit/directives/repeat.js';
import { classMap } from 'lit/directives/class-map.js';

render() {
  return html`
    &lt;ul&gt;
      ${repeat(this.items, item =&gt; item.sysId, item =&gt; html`
        &lt;li class=${classMap({ active: item.sysId === this.selectedId })}&gt;
          ${item.label}
        &lt;/li&gt;`)}
    &lt;/ul&gt;`;
}
</code></pre><hr><h2 id="contextconsumer-%E2%80%94-read-context-state"><code>ContextConsumer</code>&nbsp;— read context state</h2><p><code>import { ContextConsumer } from 'lit'</code></p><p>A&nbsp;<code>ContextConsumer</code>&nbsp;lets your widget read state published by another part of the tree. Combine with the&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/component/#lit-contexts">Lit contexts</a>&nbsp;the framework exposes —&nbsp;<code>routeContext</code>,&nbsp;<code>experienceContext</code>, etc.</p><pre><code class="language-js">new ContextConsumer(host, {
  context: routeContext,
  subscribe: true,
  callback: (value) =&gt; { /* called on every change */ },
})
</code></pre>
<!--kg-card-begin: html-->
<table data-line="79" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="79" class="code-line" dir="auto" style="position: relative;"><tr data-line="79" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Argument</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Type</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="81" class="code-line" dir="auto" style="position: relative;"><tr data-line="81" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">host</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">LitElement</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">The element that owns the consumer (usually this).</td></tr><tr data-line="82" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">context</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Context</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">The context object to read from.</td></tr><tr data-line="83" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">subscribe?</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">If true, re-fire callback on every change.</td></tr><tr data-line="84" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">callback</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">function</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Receives the context value.</td></tr></tbody></table>
<!--kg-card-end: html-->
<hr><h2 id="reactive-properties">Reactive properties</h2><p>Declared via&nbsp;<code>static properties</code>&nbsp;on your class:</p><pre><code class="language-js">class MyWidget extends AIUXWidgetElement {
  static properties = {
    title:        { type: String  },
    count:        { type: Number  },
    enabled:      { type: Boolean },
    items:        { type: Array   },
    config:       { type: Object  },
  };
}
</code></pre><p>Any change to a declared property schedules a re-render. Use&nbsp;<code>this.requestUpdate(name?, oldValue?)</code>&nbsp;to force one manually.</p><hr><h2 id="lifecycle-lit-side">Lifecycle (Lit-side)</h2><p>The standard Lit lifecycle is available on every widget. For the AIUX-specific addition&nbsp;<code>onDataChange</code>, see&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/component/#lifecycle-hooks">Component</a>.</p>
<!--kg-card-begin: html-->
<table data-line="112" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="112" class="code-line" dir="auto" style="position: relative;"><tr data-line="112" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Hook</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">When</th></tr></thead><tbody data-line="114" class="code-line" dir="auto" style="position: relative;"><tr data-line="114" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">connectedCallback()</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Element added to the DOM.</td></tr><tr data-line="115" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">disconnectedCallback()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Element removed.</td></tr><tr data-line="116" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">render()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Return a TemplateResult (required).</td></tr><tr data-line="117" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">firstUpdated(changedProperties)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">After the first render.</td></tr><tr data-line="118" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">updated(changedProperties)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">After every render.</td></tr><tr data-line="119" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">willUpdate(changedProperties)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Before update / render.</td></tr><tr data-line="120" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">shouldUpdate(changedProperties)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Return false to skip the next render.</td></tr><tr data-line="121" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">performUpdate()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Override to batch / defer.</td></tr><tr data-line="122" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">getUpdateComplete()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Override to await child updates.</td></tr><tr data-line="123" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">createRenderRoot()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Override to change render target. return this opts out of Shadow DOM.</td></tr><tr data-line="124" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">attributeChangedCallback(name, old, value)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Raw attribute change.</td></tr><tr data-line="125" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">adoptedCallback()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Element moved to a new document.</td></tr></tbody></table>
<!--kg-card-end: html-->
 ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Component ]]></title>
        <description><![CDATA[ An sn_aiux widget&#39;s component field holds a Lit web-component class that extends AIUXWidgetElement. This page covers the AIUX-specific authoring surface: the base classes, the framework-injected instance members, the lifecycle hooks, the static declarations, the services available via import, and the framework&#39;s Lit contexts and directives. ]]></description>
        <link>https://relay.semaphorepartners.com/aiux-docs/component/</link>
        <guid isPermaLink="false">6a0332462c447200013e121a</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Tue, 12 May 2026 10:02:31 -0400</pubDate>
        <media:content url="" medium="image"/>
        <content:encoded><![CDATA[ <p>An sn_aiux widget's&nbsp;<code>component</code>&nbsp;field holds a Lit web-component class that extends&nbsp;<code>AIUXWidgetElement</code>. This page covers the AIUX-specific authoring surface: the base classes, the framework-injected instance members, the lifecycle hooks, the static declarations, the services available via import, and the framework's Lit contexts and directives.</p><p>For raw Lit (<code>html</code>,&nbsp;<code>css</code>,&nbsp;<code>repeat</code>, etc.) see the&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/lit/">Lit</a>&nbsp;page. For the pre-built&nbsp;<code>&lt;aiux-*&gt;</code>&nbsp;web components see&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/daisy/">Daisy</a>. For the server side see&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/server-script/">Server Script</a>.</p><hr><h2 id="base-classes">Base classes</h2><p>Imports from&nbsp;<code>@servicenow/aiux-components-core</code>.</p>
<!--kg-card-begin: html-->
<table data-line="12" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="12" class="code-line" dir="auto" style="position: relative;"><tr data-line="12" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Symbol</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Kind</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Purpose</th></tr></thead><tbody data-line="14" class="code-line" dir="auto" style="position: relative;"><tr data-line="14" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">AIUXWidgetElement</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">class</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Base for widget components — adds this.server.*, this.aiContext, this.deps, this.trackEvent, this.logger on top of LitElement.</td></tr><tr data-line="15" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">AIUXElement</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">class</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Base for design-system components (no server data binding).</td></tr><tr data-line="16" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">WithData</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">mixin function</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">WithData(BaseClass) — opt-in server-data binding for custom base classes.</td></tr><tr data-line="17" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">UxKitElement</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">class</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Low-level base from ux_kit/core.</td></tr></tbody></table>
<!--kg-card-end: html-->
<p>Minimal widget skeleton:</p><pre><code class="language-js">import { html } from 'lit';
import { AIUXWidgetElement } from '@servicenow/aiux-components-core';

class MyWidget extends AIUXWidgetElement {
  static properties = {
    title: { type: String },
  };
  createRenderRoot() { return this; }
  render() {
    return html`&lt;h2&gt;${this.title}&lt;/h2&gt;`;
  }
}
</code></pre><hr><h2 id="instance-members-this">Instance members (<code>this.*</code>)</h2><p>Available on any&nbsp;<code>AIUXWidgetElement</code>.</p>
<!--kg-card-begin: html-->
<table data-line="42" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="42" class="code-line" dir="auto" style="position: relative;"><tr data-line="42" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Member</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Kind</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="44" class="code-line" dir="auto" style="position: relative;"><tr data-line="44" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">this.data</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">property</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Server script output payload.</td></tr><tr data-line="45" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.server.get(request)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">method</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Call server script with {action, data}. Returns Promise<response>.</response></td></tr><tr data-line="46" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.server.update()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">method</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Update server data.</td></tr><tr data-line="47" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.server.refresh()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">method</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Re-run server script and refresh this.data.</td></tr><tr data-line="48" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.aiContext</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">property</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Current AI context object.</td></tr><tr data-line="49" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.setAiContext(ctx)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">method</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Update AI context.</td></tr><tr data-line="50" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.logger</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">property</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Framework logger.</td></tr><tr data-line="51" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.trackEvent(name, data?)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">method</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Track analytics event.</td></tr><tr data-line="52" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.deps.services</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">property</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Dependency-injected services.</td></tr><tr data-line="53" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.deps.directives</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">property</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Dependency-injected directives.</td></tr><tr data-line="54" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.renderRoot</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">property</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Render root (light or shadow DOM).</td></tr><tr data-line="55" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.shadowRoot</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">property</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Shadow root.</td></tr><tr data-line="56" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.updateComplete</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">property</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Promise<boolean><span>&nbsp;</span>resolved after the next update.</boolean></td></tr><tr data-line="57" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.isConnected</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">property</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">In-DOM flag.</td></tr><tr data-line="58" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.requestUpdate(name?, oldValue?)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">method</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Force a re-render.</td></tr><tr data-line="59" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.dispatchEvent(event)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">method</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Dispatch a custom event.</td></tr><tr data-line="60" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">this.querySelector(selector)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">method</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Query a light-DOM child.</td></tr></tbody></table>
<!--kg-card-end: html-->
<hr><h2 id="lifecycle-hooks">Lifecycle hooks</h2>
<!--kg-card-begin: html-->
<table data-line="66" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="66" class="code-line" dir="auto" style="position: relative;"><tr data-line="66" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Hook</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">When</th></tr></thead><tbody data-line="68" class="code-line" dir="auto" style="position: relative;"><tr data-line="68" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">connectedCallback()</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Element added to the DOM.</td></tr><tr data-line="69" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">disconnectedCallback()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Element removed.</td></tr><tr data-line="70" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">render()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Return a TemplateResult (required).</td></tr><tr data-line="71" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">firstUpdated(changedProperties)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">After the first render.</td></tr><tr data-line="72" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">updated(changedProperties)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">After every render.</td></tr><tr data-line="73" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">willUpdate(changedProperties)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Before update / render.</td></tr><tr data-line="74" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">shouldUpdate(changedProperties)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Return false to skip the next render.</td></tr><tr data-line="75" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">performUpdate()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Override to batch / defer updates.</td></tr><tr data-line="76" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">getUpdateComplete()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Override to await child updates.</td></tr><tr data-line="77" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">createRenderRoot()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Override to change render target. return this opts out of Shadow DOM and into light DOM.</td></tr><tr data-line="78" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">attributeChangedCallback(name, old, value)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Raw attribute change.</td></tr><tr data-line="79" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">adoptedCallback()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Element moved to a new document.</td></tr><tr data-line="80" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">onDataChange(newData, oldData)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">AIUX-specific — fires when server data updates.</td></tr></tbody></table>
<!--kg-card-end: html-->
<hr><h2 id="static-class-members">Static class members</h2><pre><code class="language-js">class MyWidget extends AIUXWidgetElement {
  // Reactive properties — same as Lit
  static properties = {
    sysId: { type: String },
    count: { type: Number },
  };

  // Component-scoped CSS (alternative to the widget record's `style` field)
  static styles = css`
    :host { display: block; }
  `;

  // Inject framework services / directives
  static dependencies = { services: ['i18n', 'notifications'], directives: [] };
  // Shorthand for services-only:
  static dependencies = ['i18n', 'notifications'];

  // AI client tools — see the AI Integration section
  static client_tools = {
    refresh: {
      definition: function() { this.server.refresh(); },
      description: '[UI CONTROL] Reload the widget data.',
      arguments: [],
    },
  };
}
</code></pre><hr><h2 id="services">Services</h2><p>Top-level singletons exported from&nbsp;<code>@servicenow/aiux-services</code>,&nbsp;<code>@servicenow/aiux-utils</code>, and&nbsp;<code>@servicenow/aiux-context</code>. Import the service name to use any of its methods.</p><h3 id="i18n-%E2%80%94-translations"><code>i18n</code>&nbsp;— translations</h3><p><code>import { i18n } from '@servicenow/aiux-services'</code></p>
<!--kg-card-begin: html-->
<table data-line="125" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="125" class="code-line" dir="auto" style="position: relative;"><tr data-line="125" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Method</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Returns</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="127" class="code-line" dir="auto" style="position: relative;"><tr data-line="127" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">i18n.getMessage(key, args?)</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Get a translated string. key is a message key or {message, code, comment} object.</td></tr><tr data-line="128" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">i18n.loadMessages()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Promise<void></void></td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Bulk-load translations.</td></tr><tr data-line="129" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">i18n.loadMessage(key)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Promise<string></string></td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Load a single message by key.</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="notifications-%E2%80%94-toast-notifications"><code>notifications</code>&nbsp;— toast notifications</h3><p><code>import { notifications } from '@servicenow/aiux-services'</code></p>
<!--kg-card-begin: html-->
<table data-line="135" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="135" class="code-line" dir="auto" style="position: relative;"><tr data-line="135" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Method</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Returns</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="137" class="code-line" dir="auto" style="position: relative;"><tr data-line="137" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">notifications.add(notification)</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">string (id)</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Add a custom notification {type, message, duration?, actions?}.</td></tr><tr data-line="138" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">notifications.addInfoNotification(msg)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string (id)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Info toast.</td></tr><tr data-line="139" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">notifications.addSuccessNotification(msg)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string (id)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Success toast.</td></tr><tr data-line="140" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">notifications.addWarningNotification(msg)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string (id)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Warning toast.</td></tr><tr data-line="141" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">notifications.addErrorNotification(msg)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string (id)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Error toast.</td></tr><tr data-line="142" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">notifications.remove(id)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Remove by id.</td></tr><tr data-line="143" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">notifications.clear()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Clear all active notifications.</td></tr><tr data-line="144" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">notifications.getNotifications()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Array</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Get all active.</td></tr><tr data-line="145" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">notifications.subscribe(callback)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Unsubscribe</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Watch notification state changes.</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="locationservice-%E2%80%94-routing"><code>locationService</code>&nbsp;— routing</h3><p><code>import { locationService } from '@servicenow/aiux-services'</code></p>
<!--kg-card-begin: html-->
<table data-line="151" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="151" class="code-line" dir="auto" style="position: relative;"><tr data-line="151" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Method</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Returns</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="153" class="code-line" dir="auto" style="position: relative;"><tr data-line="153" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">locationService.navigate(path, opts?)</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Navigate to a path. opts: {replace?, state?}</td></tr><tr data-line="154" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.path()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Current path.</td></tr><tr data-line="155" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.fullPath()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Full path including experience prefix.</td></tr><tr data-line="156" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.url()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Current URL.</td></tr><tr data-line="157" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.absUrl()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Absolute URL.</td></tr><tr data-line="158" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.experience()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Current experience name.</td></tr><tr data-line="159" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.page()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Current page name.</td></tr><tr data-line="160" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.param(key)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string|null</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Single URL parameter.</td></tr><tr data-line="161" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.params()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Object</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">All route parameters as an object.</td></tr><tr data-line="162" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.search()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Search string.</td></tr><tr data-line="163" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.searchParam(key)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string|null</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Get a search param.</td></tr><tr data-line="164" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.hasSearchParam(key)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Check for a search param.</td></tr><tr data-line="165" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.updateSearchParams(params)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Merge search params.</td></tr><tr data-line="166" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.setSearchParams(params)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Replace all search params.</td></tr><tr data-line="167" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.removeSearchParam(key)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Remove one.</td></tr><tr data-line="168" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.hash()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">URL hash.</td></tr><tr data-line="169" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.setHash(hash)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Set hash.</td></tr><tr data-line="170" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.clearHash()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Clear hash.</td></tr><tr data-line="171" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.replace(path)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Replace URL without adding history.</td></tr><tr data-line="172" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.back()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Go back.</td></tr><tr data-line="173" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.subscribe(callback)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Unsubscribe</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Watch for route changes.</td></tr><tr data-line="174" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">locationService.onBeforeNavigate(callback)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Unsubscribe</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Hook before navigation; return false to cancel.</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="recordwatcherservice-%E2%80%94-reactive-gliderecord"><code>recordWatcherService</code>&nbsp;— reactive GlideRecord</h3><p><code>import { recordWatcherService } from '@servicenow/aiux-services'</code></p>
<!--kg-card-begin: html-->
<table data-line="180" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="180" class="code-line" dir="auto" style="position: relative;"><tr data-line="180" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Method</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Returns</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="182" class="code-line" dir="auto" style="position: relative;"><tr data-line="182" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">recordWatcherService.createReactiveWatcher(host, table, filter, cb)</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Watcher</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Live-watch a table/filter; callback fires on insert/update/delete.</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="themeservice-%E2%80%94-darklight-mode"><code>themeService</code>&nbsp;— dark/light mode</h3><p><code>import { themeService } from '@servicenow/aiux-services'</code></p>
<!--kg-card-begin: html-->
<table data-line="188" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="188" class="code-line" dir="auto" style="position: relative;"><tr data-line="188" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Method</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Returns</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="190" class="code-line" dir="auto" style="position: relative;"><tr data-line="190" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">themeService.getThemeMode()</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Current mode.</td></tr><tr data-line="191" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">themeService.setThemeMode(mode)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">"dark" or "light".</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="mobileappbridgeservice-%E2%80%94-native-mobile-bridge"><code>mobileAppBridgeService</code>&nbsp;— native mobile bridge</h3><p><code>import { mobileAppBridgeService } from '@servicenow/aiux-services'</code></p>
<!--kg-card-begin: html-->
<table data-line="197" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="197" class="code-line" dir="auto" style="position: relative;"><tr data-line="197" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Method</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Returns</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="199" class="code-line" dir="auto" style="position: relative;"><tr data-line="199" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">mobileAppBridgeService.isMobileApp()</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Running inside the native ServiceNow mobile app?</td></tr><tr data-line="200" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">mobileAppBridgeService.isInitialized()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Bridge ready?</td></tr><tr data-line="201" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">mobileAppBridgeService.getBridge()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Object</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Bridge instance.</td></tr><tr data-line="202" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">mobileAppBridgeService.init()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Initialize.</td></tr><tr data-line="203" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">mobileAppBridgeService.destroy()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Destroy.</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="userpreferencesservice-%E2%80%94-user-preferences"><code>userPreferencesService</code>&nbsp;— user preferences</h3><p><code>import { userPreferencesService } from '@servicenow/aiux-services'</code></p>
<!--kg-card-begin: html-->
<table data-line="209" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="209" class="code-line" dir="auto" style="position: relative;"><tr data-line="209" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Method</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Returns</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="211" class="code-line" dir="auto" style="position: relative;"><tr data-line="211" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">userPreferencesService.isReduceMotionEnabled()</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Reduced-motion preference.</td></tr><tr data-line="212" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">userPreferencesService.getUserPreference(name, default?)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">any</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Read a preference value.</td></tr><tr data-line="213" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">userPreferencesService.setUserPreference(name, value)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Set in memory.</td></tr><tr data-line="214" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">userPreferencesService.saveUserPreference(name, value)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Promise<object></object></td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Persist to server.</td></tr><tr data-line="215" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">userPreferencesService.getBooleanPreferenceValue(name, default?)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Read as boolean.</td></tr><tr data-line="216" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">userPreferencesService.subscribe(name, callback)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Unsubscribe</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Watch a preference.</td></tr><tr data-line="217" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">userPreferencesService.getUserPreferences()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Map</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">All preferences.</td></tr><tr data-line="218" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">userPreferencesService.fetchLanguages()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Promise</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Available languages.</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="arialive-%E2%80%94-screen-reader-announcements"><code>ariaLive</code>&nbsp;— screen-reader announcements</h3><p><code>import { ariaLive } from '@servicenow/aiux-services'</code></p>
<!--kg-card-begin: html-->
<table data-line="224" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="224" class="code-line" dir="auto" style="position: relative;"><tr data-line="224" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Method</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Returns</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="226" class="code-line" dir="auto" style="position: relative;"><tr data-line="226" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">ariaLive.announce(message, politeness?, options?)</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Announce. politeness: "polite" | "assertive" | "off".</td></tr><tr data-line="227" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">ariaLive.announcePolite(message, options?)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Polite announcement.</td></tr><tr data-line="228" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">ariaLive.announceAssertive(message, options?)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Urgent announcement.</td></tr><tr data-line="229" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">ariaLive.clear()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Clear pending.</td></tr><tr data-line="230" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">ariaLive.isInitialized()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">boolean</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Ready?</td></tr><tr data-line="231" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">ariaLive.initialize()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Initialize.</td></tr><tr data-line="232" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">ariaLive.destroy()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Destroy.</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="snhttp-%E2%80%94-http-client"><code>snHttp</code>&nbsp;— HTTP client</h3><p><code>import { snHttp } from '@servicenow/aiux-utils'</code></p>
<!--kg-card-begin: html-->
<table data-line="238" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="238" class="code-line" dir="auto" style="position: relative;"><tr data-line="238" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Method</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Returns</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="240" class="code-line" dir="auto" style="position: relative;"><tr data-line="240" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">snHttp.get(url, config?)</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Promise<response></response></td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">GET. config: {headers?, signal?, responseType?, batch?}.</td></tr><tr data-line="241" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">snHttp.post(url, body?, config?)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Promise<response></response></td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">POST.</td></tr><tr data-line="242" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">snHttp.put(url, body?, config?)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Promise<response></response></td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">PUT.</td></tr><tr data-line="243" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">snHttp.delete(url, config?)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Promise<response></response></td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">DELETE.</td></tr><tr data-line="244" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">snHttp.patch(url, body?, config?)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Promise<response></response></td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">PATCH.</td></tr></tbody></table>
<!--kg-card-end: html-->
<h3 id="standalone-utilities">Standalone utilities</h3><p><code>import { ... } from '@servicenow/aiux-utils'</code></p>
<!--kg-card-begin: html-->
<table data-line="250" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="250" class="code-line" dir="auto" style="position: relative;"><tr data-line="250" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Symbol</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Returns</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="252" class="code-line" dir="auto" style="position: relative;"><tr data-line="252" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">getUser()</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">{roles, userId, firstName, lastName, name, departmentID} | null</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Current user info (client-side).</td></tr><tr data-line="253" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">getSessionLanguage()</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">string</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Session language code (e.g. "en").</td></tr><tr data-line="254" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">setAiuxGlobal(key, value)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">void</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Write to window.NOW.aiux.</td></tr><tr data-line="255" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">getAiuxGlobal(key, default?)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">any</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Read from window.NOW.aiux.</td></tr><tr data-line="256" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">CHAT_CHANNEL</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">constant</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Chat channel name.</td></tr></tbody></table>
<!--kg-card-end: html-->
<hr><h2 id="lit-contexts">Lit contexts</h2><p>For use with&nbsp;<code>ContextConsumer</code>&nbsp;(see&nbsp;<a href="https://relay.semaphorepartners.com/aiux-docs/lit/">Lit</a>) to read framework state.</p><p><code>import { ... } from '@servicenow/aiux-context'</code></p>
<!--kg-card-begin: html-->
<table data-line="266" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="266" class="code-line" dir="auto" style="position: relative;"><tr data-line="266" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Context</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Holds</th></tr></thead><tbody data-line="268" class="code-line" dir="auto" style="position: relative;"><tr data-line="268" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">routeContext</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Current route info.</td></tr><tr data-line="269" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">experienceContext</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Current experience config.</td></tr><tr data-line="270" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">userPreferencesContext</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">User preferences.</td></tr><tr data-line="271" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">widgetRenderingContext</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Widget origin tracking.</td></tr><tr data-line="272" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">mobileAppBridgeContext</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Mobile bridge state.</td></tr></tbody></table>
<!--kg-card-end: html-->
<p>Example:</p><pre><code class="language-js">import { ContextConsumer } from 'lit';
import { experienceContext } from '@servicenow/aiux-context';

class MyWidget extends AIUXWidgetElement {
  experienceCtx = new ContextConsumer(this, {
    context: experienceContext,
    subscribe: true,
    callback: (value) =&gt; { this.experience = value; this.requestUpdate(); },
  });
}
</code></pre><hr><h2 id="aiux-specific-directives">AIUX-specific directives</h2><p>Lit directives specific to sn_aiux.</p><p><code>import { ... } from '@servicenow/aiux-directives'</code></p>
<!--kg-card-begin: html-->
<table data-line="297" class="code-line" dir="auto" style="border-collapse: collapse; margin-bottom: 0.7em; position: relative; color: rgb(32, 32, 32); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead data-line="297" class="code-line" dir="auto" style="position: relative;"><tr data-line="297" class="code-line" dir="auto" style="position: relative;"><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Symbol</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Kind</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Signature</th><th style="text-align: left; border-bottom: 1px solid rgba(0, 0, 0, 0.69); padding: 5px 10px; border-top-color: rgba(0, 0, 0, 0.69); border-right-color: rgba(0, 0, 0, 0.69); border-left-color: rgba(0, 0, 0, 0.69);">Description</th></tr></thead><tbody data-line="299" class="code-line" dir="auto" style="position: relative;"><tr data-line="299" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">spreadProps</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">directive</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">${spreadProps(obj)}</td><td style="padding: 5px 10px; border-color: rgba(0, 0, 0, 0.18);">Spread an object of properties/attributes onto an element.</td></tr><tr data-line="300" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">timeAgo</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">directive</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">${timeAgo(timestamp, options?)}</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Auto-updating relative time. options: {live?, formatter?}.</td></tr><tr data-line="301" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">liveRegionPolite</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">directive</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">${liveRegionPolite(options?)}</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">ARIA polite live region.</td></tr><tr data-line="302" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">liveRegionAssertive</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">directive</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">${liveRegionAssertive(options?)}</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">ARIA assertive live region.</td></tr><tr data-line="303" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">getTimeAgo</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">function</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">getTimeAgo(timestamp)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Returns {value, unit, isFuture, isJustNow}.</td></tr><tr data-line="304" class="code-line" dir="auto" style="position: relative;"><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">formatTimeAgo</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">function</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">formatTimeAgo(timeData)</td><td style="padding: 5px 10px; border-top: 1px solid rgba(0, 0, 0, 0.18); border-right-color: rgba(0, 0, 0, 0.18); border-bottom-color: rgba(0, 0, 0, 0.18); border-left-color: rgba(0, 0, 0, 0.18);">Format a getTimeAgo result as a string.</td></tr></tbody></table>
<!--kg-card-end: html-->
 ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ AIEX/Employee Slate: Everything Knowledge26 Didn&#x27;t Tell You ]]></title>
        <description><![CDATA[ Ten years ago, ServiceNow announced Service Portal at Knowledge, and the conference actually told you how it worked. There were sessions on writing widgets, deep dives on the server script API, lab seats you could walk into. The API surface — sp_widget, sp_portal, $sp — wasn&#39;t massively complex, ]]></description>
        <link>https://relay.semaphorepartners.com/articles/aiex-slate-everything-knowledge26-didnt-tell-you/</link>
        <guid isPermaLink="false">6a023f5b0e17080001bc6bda</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Mon, 11 May 2026 18:14:47 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/05/aiex_slate_k26.png" medium="image"/>
        <content:encoded><![CDATA[ <p>Ten years ago, ServiceNow announced Service Portal at Knowledge, and the conference actually told you how it worked. There were sessions on writing widgets, deep dives on the server script API, lab seats you could walk into. The API surface —&nbsp;<code>sp_widget</code>,&nbsp;<code>sp_portal</code>,&nbsp;<code>$sp</code>&nbsp;— wasn't massively complex, but you left a session, opened a tab, and wrote code that ran. I flew home from Vegas with the docs cached and spent the flight on my first widgets. By the time I landed I had a passable clone of the homepage I'd been staring at on the conference WiFi. That weekend I built three more. The framework rewarded curiosity.</p><p>This year I flew home from Knowledge and the contrast was loud. The buzzword was&nbsp;<strong>Employee</strong> <strong>Slate</strong>, the decks were beautiful, the demos were — in more than one case —&nbsp;<em>static HTML files</em>. Not early-build prototypes with a stubbed backend. Actual screenshotted UIs masquerading as working software. No sessions on how the framework underneath Slate worked. Nothing meaningful to cache. Same product-deck energy as Helsinki, none of the substance. The framework itself — which, spoiler, is called&nbsp;<code>sn_aiux</code>&nbsp;and is the actually interesting object here — is shipping right now on Zurich Patch 9, and it is, depending on how you count, the third time in a decade ServiceNow has reinvented its portal story. (If you don't count UI Builder, it's the second. ServiceNow's product marketing seems to be going with "second.") I figured I'd repeat the experiment: open the bundle on the plane, see how it works, write down what I found.</p><p>The short version:&nbsp;<strong>Employee</strong> <strong>Slate is one experience built on a new framework called&nbsp;<code>sn_aiux</code>.</strong>&nbsp;Not the framework itself. The framework is the interesting thing. It's a Lit.js + Tailwind + DaisyUI runtime with a deliberate bridge that embeds your existing Angular Service Portal widgets inside Lit components. There's a Service Portal record (<code>sp_portal</code>) sitting next to the framework as a sidecar — it carries the theme that reskins those embedded SP widgets so they don't look like 2016-era Bootstrap dropped into a Tailwind page — but sn_aiux is not running on Service Portal. Nothing you've already built has to be thrown away. New work happens in a stack that's much more familiar to anyone who's written modern web components in the last five years.</p><p>This post is the architectural map I wish ServiceNow had handed out.</p><blockquote><strong>Availability.</strong>&nbsp;sn_aiux ships on&nbsp;<strong>Zurich Patch 9</strong>. Three store apps must be installed:&nbsp;<code>sn_aiux_ia_config</code>,&nbsp;<code>sn_aiux_builder</code>,&nbsp;<code>sn_aiux_components</code>.&nbsp;</blockquote><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://store.servicenow.com/store/app/947ced2b4760cf904db76411516d43b5?ref=relay.semaphorepartners.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">AI Experience Framework Components - ServiceNow Store</div><div class="kg-bookmark-description">ServiceNow App Store: AI Experience Framework Components by ServiceNow - Common experience components for the AI Experience Framework</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://static.ghost.org/v5.0.0/images/link-icon.svg" alt=""><span class="kg-bookmark-author">ServiceNow Store</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://store.servicenow.com/images/storeui/servicenow-logo-header.svg" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://store.servicenow.com/store/app/236c652b972c4f90f17933e11153afd1?ref=relay.semaphorepartners.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">AI Experience Framework Builder - ServiceNow Store</div><div class="kg-bookmark-description">ServiceNow App Store: AI Experience Framework Builder by ServiceNow - Widget creation and playground experience for ServiceNow AIX widgets</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://static.ghost.org/v5.0.0/images/link-icon.svg" alt=""><span class="kg-bookmark-author">ServiceNow Store</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://store.servicenow.com/images/storeui/servicenow-logo-header.svg" alt="" onerror="this.style.display = 'none'"></div></a></figure><hr><h2 id="the-naming-untangled">The naming, untangled</h2><p>Walking out of Knowledge, I assumed "Slate" was ServiceNow's new word for "portal" — the framework name, the way Service Portal was. It isn't. And to be fair, "Service Portal" wasn't really the framework name either, even though everyone used it that way. The framework called Service Portal shipped with a default portal also called Service Portal, mounted at&nbsp;<code>/sp</code>. That collision haunted every customer conversation for years.&nbsp;<em>"Service Portal the framework, or Service Portal the portal? The one at /sp, or your portal?"</em>&nbsp;It took ServiceNow several releases to ship Employee Center — a real branded employee intranet built on top — and only then did the naming start to separate. The framework was Service Portal. The portal was Employee Center.</p><p>sn_aiux is the same shape, told more clearly. The framework is&nbsp;<code>sn_aiux</code>&nbsp;in the codebase (written&nbsp;<strong>AIEX</strong>&nbsp;in marketing material — when you see&nbsp;<code>/sncapps/aix/...</code>,&nbsp;<code>sys_aix_*</code>&nbsp;tables, or&nbsp;<code>&lt;aiux-angular-element&gt;</code>, that's sn_aiux internals). Slate is the headline experience built on top, analogous to Employee Center — a real, branded, default landing destination. The mapping to what you already know:</p><ul><li><em>Service Portal</em>&nbsp;is to&nbsp;<em>sn_aiux</em>&nbsp;what&nbsp;<em>framework</em>&nbsp;is to&nbsp;<em>framework</em>.</li><li><em>An sp_portal record</em>&nbsp;(Employee Center, ESS, whatever) is to&nbsp;<em>a sys_aix_experience record</em>&nbsp;(Slate, your custom landing experience, the Builder itself) what&nbsp;<em>a portal</em>&nbsp;is to&nbsp;<em>an experience</em>.</li><li>Pages, widgets, themes — all the supporting concepts have direct equivalents.</li></ul><p>The fact that ServiceNow led with the experience name at Knowledge and barely mentioned the framework is, at this point, a pattern.</p><hr><h2 id="where-experiences-live">Where experiences live</h2><p>I didn't start by digging through Service Portal — but somewhere in the first hour I noticed an&nbsp;<code>sp_portal</code>&nbsp;record titled "AIUX Portal" (sys_id&nbsp;<code>7cf6e70a3f123210860f2248001f8b63</code>, url_suffix&nbsp;<code>aiuxsp</code>) and felt a small wave of dread. ServiceNow has form for stuffing new technology into old tables and calling it modernization. If&nbsp;<code>sn_aiux</code>&nbsp;experiences were going to be&nbsp;<code>sp_portal</code>&nbsp;rows with extra columns and a Lit veneer, this was going to be a very different post.</p><p>Mercifully, they didn't. That&nbsp;<code>sp_portal</code>&nbsp;record isn't where sn_aiux experiences live, and it isn't a runtime host either. It's a sidecar. Its job is to carry the theme that re-skins embedded SP widgets when they get bridged into sn_aiux pages — so an Angular catalog widget renders against Tailwind/DaisyUI tokens instead of Bootstrap defaults — and to provide the portal-scoped&nbsp;<code>$sp</code>&nbsp;context for server scripts that ask for it. Hit&nbsp;<code>/</code>aiuxsp&nbsp;directly and you get a standard service portal homepage almost the same as where Helsinki left it. The actual experience records live in&nbsp;<strong><code>sys_aix_experience</code></strong>, addressable through a different config endpoint:</p><pre><code>GET /api/now/aix/config/&lt;url_suffix&gt;
Accept: multipart/mixed
</code></pre><p>With&nbsp;<code>aiuxsp</code>&nbsp;as the suffix, that endpoint returns 400 "Experience not found" — confirming the&nbsp;<code>sp_portal</code>&nbsp;record isn't an experience. With&nbsp;<code>builder</code>&nbsp;as the suffix, it returns 200 and a full JSON payload describing the SN AIUX Builder experience: its sys_id, theme, telemetry config, landing path, and (interestingly) a list of URL rewrite rules. We'll come back to those.</p><p>The URL pattern for any sn_aiux experience comes straight from the bundle:</p><pre><code class="language-js">DEFAULT_PATTERN = new URLPattern({
  pathname: `/${vHostSiteName}/:experience/:page*`
});
</code></pre><p><code>vHostSiteName</code>&nbsp;is the constant&nbsp;<code>"aiux"</code>. Which means every experience URL is:</p><pre><code>/aiux/&lt;experience_url_suffix&gt;/&lt;page_path&gt;
</code></pre><p>The Builder is at&nbsp;<code>/aiux/builder/widgets</code>. </p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/05/05-builder-widgets-final.png" class="kg-image" alt="" loading="lazy" width="2000" height="1322" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2026/05/05-builder-widgets-final.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2026/05/05-builder-widgets-final.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2026/05/05-builder-widgets-final.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w2400/2026/05/05-builder-widgets-final.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Slate is at its own suffix. Build your own experience with&nbsp;<code>url_suffix=esc</code>&nbsp;and it's at&nbsp;<code>/aiux/esc/...</code>. Cleanly path-based, deep-linkable, no query strings involved. There is no&nbsp;<code>?experience=</code>&nbsp;escape hatch — I checked, the entry servlet (<code>/$ai_experience.do</code>) returns byte-identical HTML for every query variation I threw at it. The experience comes from the URL pathname, full stop.</p><p>Employee Slate is at&nbsp;<code>/aiux/employeeslate</code>. </p><hr><h2 id="the-schema-in-one-paragraph">The schema in one paragraph</h2><p>Twenty-nine&nbsp;<code>sys_aix_*</code>&nbsp;tables back the framework, all in the&nbsp;<code>global</code>&nbsp;scope.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/05/page-13-sys-aix-tables-list.png" class="kg-image" alt="" loading="lazy" width="2000" height="1608" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2026/05/page-13-sys-aix-tables-list.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2026/05/page-13-sys-aix-tables-list.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2026/05/page-13-sys-aix-tables-list.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w2400/2026/05/page-13-sys-aix-tables-list.png 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="tables"> Tables</h3>
<!--kg-card-begin: html-->
<table border="0" cellpadding="0" cellspacing="0" width="592" style="border-collapse:
 collapse;width:500pt">
<!--StartFragment-->
 <colgroup><col width="296" span="2" style="mso-width-source:userset;mso-width-alt:9472;
 width:222pt">
 </colgroup><tbody><tr height="20" style="height:15.0pt">
  <td height="20" class="xl66" width="296" style="height:15.0pt;width:222pt">Label</td>
  <td class="xl66" width="310" style="width:300pt">Name</td>
 </tr>
 <tr height="43" style="height:32.0pt">
  <td height="43" class="xl65" width="296" style="height:32.0pt;width:222pt">AI
  Experience</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_experience</td>
 </tr>
 <tr height="85" style="height:64.0pt">
  <td height="85" class="xl65" width="296" style="height:64.0pt;width:222pt">AI
  Experience notification content</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_notification_content</td>
 </tr>
 <tr height="128" style="height:96.0pt">
  <td height="128" class="xl65" width="296" style="height:96.0pt;width:222pt">AI
  Experience notification content configuration</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_notification_content_config</td>
 </tr>
 <tr height="64" style="height:48.0pt">
  <td height="64" class="xl65" width="296" style="height:48.0pt;width:222pt">AI
  Experience Properties</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_experience_properties</td>
 </tr>
 <tr height="43" style="height:32.0pt">
  <td height="43" class="xl65" width="296" style="height:32.0pt;width:222pt">AIX App
  Shell</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_app_shell</td>
 </tr>
 <tr height="85" style="height:64.0pt">
  <td height="85" class="xl65" width="296" style="height:64.0pt;width:222pt">AIX
  Bundle Dependency M2M</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_m2m_bundle_dependency</td>
 </tr>
 <tr height="43" style="height:32.0pt">
  <td height="43" class="xl65" width="296" style="height:32.0pt;width:222pt">AIX
  Color Swatch</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_color_swatch</td>
 </tr>
 <tr height="43" style="height:32.0pt">
  <td height="43" class="xl65" width="296" style="height:32.0pt;width:222pt">AIX
  Container</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_container</td>
 </tr>
 <tr height="43" style="height:32.0pt">
  <td height="43" class="xl65" width="296" style="height:32.0pt;width:222pt">AIX
  Dashboard</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_dashboard</td>
 </tr>
 <tr height="64" style="height:48.0pt">
  <td height="64" class="xl65" width="296" style="height:48.0pt;width:222pt">AIX
  Dashboard Item</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_dashboard_item</td>
 </tr>
 <tr height="85" style="height:64.0pt">
  <td height="85" class="xl65" width="296" style="height:64.0pt;width:222pt">AIX
  Dashboard Personalization Item</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_dashboard_personalization_item</td>
 </tr>
 <tr height="64" style="height:48.0pt">
  <td height="64" class="xl65" width="296" style="height:48.0pt;width:222pt">AIX
  Dependency</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_dependency</td>
 </tr>
 <tr height="64" style="height:48.0pt">
  <td height="64" class="xl65" width="296" style="height:48.0pt;width:222pt">AIX
  Dependency Bundle</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_dependency_bundle</td>
 </tr>
 <tr height="64" style="height:48.0pt">
  <td height="64" class="xl65" width="296" style="height:48.0pt;width:222pt">AIX
  Entity Widget Mapping</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_entity_widget_mapping</td>
 </tr>
 <tr height="85" style="height:64.0pt">
  <td height="85" class="xl65" width="296" style="height:64.0pt;width:222pt">AIX
  Experience Page Relation</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_experience_page_rel</td>
 </tr>
 <tr height="43" style="height:32.0pt">
  <td height="43" class="xl65" width="296" style="height:32.0pt;width:222pt">AIX
  Layout</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_layout</td>
 </tr>
 <tr height="85" style="height:64.0pt">
  <td height="85" class="xl65" width="296" style="height:64.0pt;width:222pt">AIX M2M
  Experience Dashboard</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_m2m_experience_dashboard</td>
 </tr>
 <tr height="43" style="height:32.0pt">
  <td height="43" class="xl65" width="296" style="height:32.0pt;width:222pt">AIX Menu</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_menu</td>
 </tr>
 <tr height="43" style="height:32.0pt">
  <td height="43" class="xl65" width="296" style="height:32.0pt;width:222pt">AIX Menu
  Item</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_menu_item</td>
 </tr>
 <tr height="64" style="height:48.0pt">
  <td height="64" class="xl65" width="296" style="height:48.0pt;width:222pt">AIX Menu
  Item Category</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_menu_item_category</td>
 </tr>
 <tr height="43" style="height:32.0pt">
  <td height="43" class="xl65" width="296" style="height:32.0pt;width:222pt">AIX Page</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_page</td>
 </tr>
 <tr height="64" style="height:48.0pt">
  <td height="64" class="xl65" width="296" style="height:48.0pt;width:222pt">AIX Page
  Route Map</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_page_route_map</td>
 </tr>
 <tr height="43" style="height:32.0pt">
  <td height="43" class="xl65" width="296" style="height:32.0pt;width:222pt">AIX
  Theme</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_theme</td>
 </tr>
 <tr height="43" style="height:32.0pt">
  <td height="43" class="xl65" width="296" style="height:32.0pt;width:222pt">AIX URL
  Rewrite Rule</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_url_rewrite_rule</td>
 </tr>
 <tr height="43" style="height:32.0pt">
  <td height="43" class="xl65" width="296" style="height:32.0pt;width:222pt">AIX
  widget</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_widget</td>
 </tr>
 <tr height="64" style="height:48.0pt">
  <td height="64" class="xl65" width="296" style="height:48.0pt;width:222pt">AIX
  Widget Cache Buster</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_widget_cache_buster</td>
 </tr>
 <tr height="64" style="height:48.0pt">
  <td height="64" class="xl65" width="296" style="height:48.0pt;width:222pt">AIX
  Widget Dependency</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_m2m_widget_dependency</td>
 </tr>
 <tr height="85" style="height:64.0pt">
  <td height="85" class="xl65" width="296" style="height:64.0pt;width:222pt">AIX
  Widget Dependency Bundle</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_m2m_widget_dependency_bundle</td>
 </tr>
 <tr height="64" style="height:48.0pt">
  <td height="64" class="xl65" width="296" style="height:48.0pt;width:222pt">AIX
  Widget Instance</td>
  <td class="xl65" width="296" style="width:222pt">sys_aix_widget_instance</td>
 </tr>
<!--EndFragment-->
</tbody></table>
<!--kg-card-end: html-->
<p>Two inheritance signals are worth lingering on.&nbsp;<strong><code>sys_aix_widget</code>&nbsp;extends&nbsp;<code>sys_ux_widget</code></strong>&nbsp;—&nbsp;<em>not</em>&nbsp;<code>sp_widget</code>. That's structurally significant but not historically loaded:&nbsp;<code>sys_ux_widget</code>&nbsp;doesn't have meaningful tenancy outside sn_aiux today, so the inheritance is more about declaring family than joining an existing one. What's notable is what&nbsp;<em>didn't</em>&nbsp;come along for the ride: sn_aiux did&nbsp;<strong>not</strong>&nbsp;inherit UI Builder's metadata-in-tables philosophy. Your widget's component lives in a single&nbsp;<code>component</code>&nbsp;field as readable Lit source, not scattered across fifteen rows of&nbsp;<code>sys_ux_*</code>&nbsp;records you have to assemble in your head to understand what the thing does. Everyone who looked at UIB, tried to author a macroponent, and quietly closed the tab: this is your moment of vindication. The good idea from that lineage (modern web components) is here. The bad idea (the component as a relational graph) didn't make the trip.</p><p>The other inheritance worth noticing is&nbsp;<strong><code>sys_aix_notification_content_config</code>&nbsp;extending&nbsp;<code>sys_notification_content</code></strong>&nbsp;— joining the existing notification-content rows for Service Portal, Next Experience, Virtual Agent, and Workspace. sn_aiux is plugging into a shared platform-wide notification family rather than reimplementing one. Most other AIEX tables extend&nbsp;<code>sys_metadata</code>, so everything is update-set-capturable for promotion. Good news for migrations.</p><p>One concept sn_aiux&nbsp;<em>did</em>&nbsp;borrow from UI Builder that Service Portal never had:&nbsp;<strong><code>sys_aix_app_shell</code></strong>. It's a level above the experience — a chrome/header/navigation wrapper shared across multiple experiences. UIB has the same idea for its UX apps; Service Portal sort of expected you to handle it inside every portal's theme and header widgets. Having a dedicated record for it is the right call, especially if you're going to ship more than one experience under a single brand.</p><p>The Application Navigator menu reinforces all of this. Once the apps are installed, you get a fresh module called&nbsp;<strong>AI Experience Framework (AIUX)</strong>&nbsp;with sub-modules for&nbsp;<strong>Experiences, Pages, Containers, Widgets, Widget Instances, Widget Dependencies, Dashboards, Menus, and Themes</strong>.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/05/app_nav_aiux.png" class="kg-image" alt="" loading="lazy" width="606" height="862" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2026/05/app_nav_aiux.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/05/app_nav_aiux.png 606w"></figure><p> Read that list back to yourself slowly. Anyone who's spent more than ten minutes inside the Service Portal application navigator should be having a strong sense of déjà vu. The shape of the framework is, very deliberately, the shape of the framework people already know. ServiceNow has clearly internalized that the path to adoption runs through familiarity, and it built the navigation accordingly.</p><hr><h2 id="the-widget-in-detail">The widget, in detail</h2><p>A&nbsp;<code>sys_aix_widget</code>&nbsp;record looks a lot like an&nbsp;<code>sp_widget</code>&nbsp;record if you squint, with a few additions. Same&nbsp;<code>id</code>&nbsp;slug. Same&nbsp;<code>name</code>,&nbsp;<code>description</code>,&nbsp;<code>style</code>. The server&nbsp;<code>script</code>&nbsp;field uses the&nbsp;<em>identical</em>&nbsp;IIFE signature —&nbsp;<code>(function(data, options, input) { ... })(data, options, input)</code>&nbsp;— and inside that script you have the same&nbsp;<code>gs.*</code>,&nbsp;<code>GlideRecord</code>,&nbsp;<code>GlideSPScriptable</code>&nbsp;APIs you've always had. The Activity Stream widget literally instantiates&nbsp;<code>new GlideSPScriptable("7cf6e70a3f...")</code>, the same&nbsp;<code>aiuxsp</code>&nbsp;portal sys_id we found earlier. This is where the sidecar earns its keep: an sn_aiux widget that wants a portal-scoped&nbsp;<code>$sp</code>&nbsp;reaches into the SP record next door. Not every widget needs it — pure-Lit widgets ignore the sidecar entirely — but the option is there for the ones that do.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/05/page-11c-activity-stream-server-script-tab.png" class="kg-image" alt="" loading="lazy" width="2000" height="1608" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2026/05/page-11c-activity-stream-server-script-tab.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2026/05/page-11c-activity-stream-server-script-tab.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2026/05/page-11c-activity-stream-server-script-tab.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w2400/2026/05/page-11c-activity-stream-server-script-tab.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>What's different is the&nbsp;<code>component</code>&nbsp;field. Instead of an Angular template plus a controller function, it holds a&nbsp;<strong>Lit.js web-component class</strong>:</p><pre><code class="language-js">import { html } from 'lit';
import { AIUXWidgetElement } from '@servicenow/aiux-components-core';

class MyWidget extends AIUXWidgetElement {
  static properties = { sysId: { type: String } };
  render() {
    return html`&lt;div class="aiux-card"&gt;...&lt;/div&gt;`;
  }
}
</code></pre><p>The styling system is Tailwind under DaisyUI, both bundled and prefixed under&nbsp;<code>aiux-*</code>. If you've ever written&nbsp;<code>lit-html</code>&nbsp;and reached for DaisyUI's&nbsp;<code>card</code>,&nbsp;<code>btn</code>, or&nbsp;<code>modal</code>&nbsp;semantic classes, you already know 90% of the surface area. The learning curve is real but short.</p><p>Two new fields show up that Service Portal never had.&nbsp;<code>best_for</code>&nbsp;is a natural-language description of when to use the widget — engineered for an AI agent to read and decide whether to invoke.&nbsp;<code>client_tools</code>&nbsp;is a JSON manifest of UI actions the widget exposes back to the agent. Activity Stream's&nbsp;<code>client_tools</code>&nbsp;declares a&nbsp;<code>postComment</code>&nbsp;tool whose description starts with&nbsp;<code>[IMMEDIATE ACTION - UI CONTROL]</code>&nbsp;and lists keyword triggers. It's a typed contract between the widget and the conversational layer.</p><hr><h2 id="the-service-portal-bridge">The Service Portal bridge</h2><p>The single most important architectural decision in sn_aiux is this:&nbsp;<strong>it doesn't replace&nbsp;<code>sp_widget</code>. It embeds it.</strong></p><p>Three of the nineteen out-of-the-box widgets are pure adapters around existing Angular Service Portal widgets. The Form widget (<code>form-widget</code>) wraps&nbsp;<code>widget-form</code>. The Catalog Item widget wraps&nbsp;<code>widget-sc-cat-item-v2</code>. The Order Guide widget wraps&nbsp;<code>widget-sc-order-guide-v2</code>. The pattern is the same in all three: a few lines of Lit that read URL params, build an options object, and hand it off to a custom element called&nbsp;<code>&lt;aiux-angular-element&gt;</code>.</p><p>Here's the entire Form widget&nbsp;<code>component</code>&nbsp;</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/05/page-08b-form-widget-component-tab.png" class="kg-image" alt="" loading="lazy" width="2000" height="1608" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2026/05/page-08b-form-widget-component-tab.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2026/05/page-08b-form-widget-component-tab.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2026/05/page-08b-form-widget-component-tab.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w2400/2026/05/page-08b-form-widget-component-tab.png 2400w" sizes="(min-width: 720px) 720px"></figure><pre><code class="language-js">import { html } from 'lit';
import { AIUXWidgetElement } from '@servicenow/aiux-components-core';
import { locationService } from '@servicenow/aiux-services';

class FormWidget extends AIUXWidgetElement {
  createRenderRoot() { return this; }
  constructor() {
    super();
    const params = locationService.params();
    this.options = {
      table: params.table,
      sys_id: params.sys_id,
      hideRelatedLists: true,
    };
  }
  render() {
    return html`
      &lt;aiux-angular-element
        .widgetId=${'widget-form'}
        .options=${this.options}&gt;
      &lt;/aiux-angular-element&gt;`;
  }
}
</code></pre><p>That's it.&nbsp;<code>&lt;aiux-angular-element&gt;</code>&nbsp;is the runtime adapter. At mount time it bootstraps the embedded AngularJS Service Portal engine inside the Lit DOM, looks up&nbsp;<code>widget-form</code>&nbsp;by its&nbsp;<code>sp_widget.id</code>, runs that widget's server script with the supplied&nbsp;<code>options</code>, and renders its Angular template inside the modern shell. The Form widget's own server script is empty — there's nothing for it to do.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/05/page-08c-form-widget-server-script-tab.png" class="kg-image" alt="" loading="lazy" width="2000" height="1608" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2026/05/page-08c-form-widget-server-script-tab.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2026/05/page-08c-form-widget-server-script-tab.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2026/05/page-08c-form-widget-server-script-tab.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w2400/2026/05/page-08c-form-widget-server-script-tab.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>This is the migration story. Your Service Portal investment isn't deprecated. For any widget that already works — and especially the complex ones with cart logic, variable trees, form-engine bindings — you wrap it in twenty lines of Lit and ship it inside an sn_aiux experience. New work happens in pure Lit. The two live in the same page.</p><hr><h2 id="porting-a-real-widget-the-cool-clock">Porting a real widget: the Cool Clock</h2><p>The bridge pattern is easier to internalize when you actually use it on something real, so let's pick a widget every long-time Service Portal developer has seen:&nbsp;<strong>Cool Clock</strong>&nbsp;(<code>widget-cool-clock</code>). It ships out of the box on every instance — an analog clock face with a configurable timezone and a configurable color for the second hand. Its&nbsp;<code>option_schema</code>&nbsp;is exactly two fields:</p><pre><code class="language-json">[
  {
    "name": "zone",
    "default_value": "America/Los_Angeles",
    "section": "Data",
    "label": "TimeZone",
    "type": "string"
  },
  {
    "name": "c_color",
    "default_value": "red",
    "section": "Presentation",
    "label": "Second hand color",
    "type": "string"
  }
]</code></pre><p>We are not going to rewrite Cool Clock. The whole point of the exercise is that we&nbsp;<em>don't have to</em>. The widget already exists, it already works in Service Portal, and&nbsp;<code>&lt;aiux-angular-element&gt;</code>&nbsp;will run it unchanged. All we're building is the sn_aiux adapter.</p><p>Open&nbsp;<code>/aiux/builder/widgets</code>&nbsp;and create a new&nbsp;<code>sys_aix_widget</code>:</p><ul><li><strong>Custom element name (id):</strong>&nbsp;<code>cool-clock-aix</code></li><li><strong>Widget name:</strong>&nbsp;Cool Clock</li><li><strong>Description:</strong>&nbsp;Bridges the OOB&nbsp;<code>widget-cool-clock</code>&nbsp;Service Portal widget into an sn_aiux experience.</li></ul><p>Set the&nbsp;<code>component</code>&nbsp;field to:</p><pre><code class="language-js">import { html } from 'lit';
import { AIUXWidgetElement } from '@servicenow/aiux-components-core';

class CoolClockAix extends AIUXWidgetElement {
  static properties = {
    zone:    { type: String },
    c_color: { type: String },
  };

  createRenderRoot() { return this; }

  constructor() {
    super();
    this.zone = 'America/Los_Angeles';
    this.c_color = 'red';
  }

  render() {
    const options = {
      zone:    this.zone,
      c_color: this.c_color,
    };
    return html`
      &lt;aiux-angular-element
        .widgetId=${'widget-cool-clock'}
        .options=${options}&gt;
      &lt;/aiux-angular-element&gt;`;
  }
}
</code></pre><p>Set the&nbsp;<code>input_schema</code>&nbsp;to mirror the SP widget's options so the Builder UI can configure them:</p><pre><code class="language-json">{
  "zone":    { "type": "String", "description": "TimeZone (e.g. America/Los_Angeles)" },
  "c_color": { "type": "String", "description": "Second hand color" }
}
</code></pre><p>Leave the server&nbsp;<code>script</code>&nbsp;field as the empty IIFE. The original&nbsp;<code>widget-cool-clock</code>&nbsp;already has its own server script and the bridge will run it.</p><p>Drop&nbsp;<code>cool-clock-aix</code>&nbsp;onto a&nbsp;<code>sys_aix_page</code>, set&nbsp;<code>zone</code>&nbsp;and&nbsp;<code>c_color</code>&nbsp;from the inline configuration, and reload. The Lit wrapper mounts.&nbsp;<code>&lt;aiux-angular-element&gt;</code>&nbsp;boots the embedded Angular runtime. The runtime looks up&nbsp;<code>widget-cool-clock</code>&nbsp;by its&nbsp;<code>sp_widget.id</code>, runs its server script with&nbsp;<code>options = { zone, c_color }</code>, and renders the existing template inside your sn_aiux page. The clock ticks. The second hand is the color you asked for. The timezone is the one you asked for. Nothing about the widget itself changed.</p><p>That's the whole pattern. A widget that has shipped on every Service Portal instance for the better part of a decade now runs inside sn_aiux with zero modifications and roughly twenty-five lines of Lit. Multiply that by every widget in your existing portal and you can start to see why the bridge is a bigger deal than the rest of the framework combined.</p><hr><h2 id="the-migration-tax-in-theory">The migration tax, in theory</h2><p>The config payload from&nbsp;<code>/api/now/aix/config/builder</code>&nbsp;includes an array I almost skipped past on first read. It looks like this:</p><pre><code class="language-json">{ "source_type": "portal_page", "portal_page": "kb_article",
  "parameter_mapping": { "sys_id": "sys_id" },
  "to_aiux_page": "/knowledge/:sys_id" },

{ "source_type": "processor", "processor": "kb_view.do",
  "parameter_mapping": { "sys_kb_id": "article_id" },
  "to_aiux_page": "/knowledge/:article_id" },

{ "source_type": "processor", "processor": "sc_cat_item.do",
  "parameter_mapping": { "sys_id": "item_id" },
  "to_aiux_page": "/catalog/:item_id" }
</code></pre><p>These records live in&nbsp;<code><strong>sys_aix_url_rewrite_rule</strong></code>. Two&nbsp;<code>source_type</code>&nbsp;values appear in the OOB data:&nbsp;<code>portal_page</code>&nbsp;(a hit on a Service Portal&nbsp;<code>sp_page</code>) and&nbsp;<code>processor</code>&nbsp;(a hit on a legacy&nbsp;<code>.do</code>&nbsp;processor URL). The intent is clear: intercept incoming legacy URLs and translate them into the equivalent sn_aiux paths so existing deep links —&nbsp;<code>kb_view.do?sys_kb_id=XYZ</code>,&nbsp;<code>sc_cat_item.do?sys_id=ABC</code>, a custom processor someone wrote years ago — keep resolving after you switch an experience over.</p><p>That's the&nbsp;<em>intent</em>. I want to be honest that I haven't fully confirmed it's working end-to-end yet. The table is present, the rule shape is sensible, the config endpoint returns the rules — but the actual rewriter component that consumes them is somewhere I haven't traced, and I haven't tested a clean cutover with a legacy URL coming in from email. File this one under "promising mechanism, verify before you bet a rollout on it." Worth keeping a close eye on the table, the relevant&nbsp;<code>sys_aix_url_rewrite_rule</code>&nbsp;records, and the rewrite behavior under the network panel before you tell stakeholders the old links will just work.</p><hr><h2 id="the-404-page-is-a-breakout-game-again">The 404 page is a Breakout game (again)</h2><p>Anyone who spent enough time poking at the default&nbsp;<code>/sp</code>&nbsp;portal back in the day knows that the Service Portal 404 page also rendered a Breakout game. It was a tiny piece of personality buried in&nbsp;<code>sp_page</code>&nbsp;and the matching widget, and most customers stripped it out before going to production. sn_aiux brings it back, and this is the homage I didn't know I needed.</p><p>Navigate to&nbsp;<code>/aiux/builder</code>&nbsp;(no page path) and the experience tries to land on&nbsp;<code>/home</code>. There is no page in&nbsp;<code>sys_aix_page</code>&nbsp;with&nbsp;<code>path_pattern: /home</code>&nbsp;for the Builder experience, so the router falls through to the 404 page (<code>/not-found</code>). The 404 page embeds a widget called&nbsp;<code>breakout-game</code>, which is a fully implemented, fully playable clone of Atari Breakout — scoreboard, lives counter, paddle, the works (<em>02-builder-home-404-breakout.png</em>,&nbsp;<em>page-12-breakout-editor-preview.png</em>).</p><p>It's also a real widget in&nbsp;<code>sys_aix_widget</code>, with a real description that gives the new joke away:&nbsp;<em>"Demonstrating client tools functionality and interactive widget capabilities. A fun example widget that showcases how AI agents can interact with widgets through client tools."</em>&nbsp;The Breakout widget exposes four&nbsp;<code>client_tools</code>&nbsp;—&nbsp;<code>reset-game</code>,&nbsp;<code>pause-game</code>,&nbsp;<code>get-game-state</code>,&nbsp;<code>set-difficulty</code>&nbsp;— so on top of being a nostalgic callback, the 404 page is now doing double duty as the framework's onboarding demo for the agent-widget interaction model.</p><p>I respect the continuity. A small thing, but it tells you where the framework's center of gravity is: every page is a widget composition, including the system's own error states, and every widget that can be configured can also be driven by an AI. And ten years later, someone at ServiceNow still thinks the right easter egg is Breakout.</p><hr><h2 id="what-i-took-away">What I took away</h2><p>After two and a half hours and some altitude, the picture that settled out is this. sn_aiux is ServiceNow's third swing at the portal problem in ten years — though, again, the official scoreboard reads "second." What's striking is how deliberately it leans back into the parts of Service Portal that&nbsp;<em>worked</em>&nbsp;and that customers built a mountain of stuff on. The server-script model is unchanged.&nbsp;<code>gs.*</code>,&nbsp;<code>GlideRecord</code>,&nbsp;<code>GlideSPScriptable</code>&nbsp;all behave the way they always have. The widget unit-of-authoring — a record with a server script, a template, a stylesheet, an option schema — is the same shape, just modernized underneath. Angular-1 templates become Lit web components. Bootstrap CSS becomes Tailwind/DaisyUI. The application-navigator menu is, structurally, the Service Portal menu re-photographed. None of that is accidental.</p><p>It's also a very ServiceNow move in the bigger sense. The platform has a long-running habit of bringing the old thing along while embracing the new one — Update Sets that survived a dozen rewrites, scoped applications grafted onto a global-scope foundation, Flow Designer running alongside Workflow Editor for years. sn_aiux fits the pattern. Your existing&nbsp;<code>sp_widget</code>&nbsp;library isn't deprecated; it's wrapped. Your&nbsp;<code>sys_metadata</code>&nbsp;extensions promote the same way they always did. The bridge layer (<code>&lt;aiux-angular-element&gt;</code>) is a first-class runtime feature, not a forgotten compatibility shim, and that single decision is what makes adoption a non-decision rather than a project.</p><p>If sn_aiux had told everyone&nbsp;<em>rebuild your widgets</em>, adoption would have stalled the way Now Experience adoption did. Instead, ServiceNow let you keep every Angular widget you've ever written and surface it in the new world with twenty lines of glue. The framework is compelling enough on its own merits — the AI surface, the URL routing, the Builder UX — but the bridge is what makes "should we" stop being the interesting question.</p><p>The thing I'd do first, if I were starting a real project on this: pick one Service Portal page that gets a lot of traffic, stand up a new&nbsp;<code>sys_aix_experience</code>, wrap the existing widgets with the Cool Clock pattern, populate&nbsp;<code>sys_aix_url_rewrite_rule</code>&nbsp;so the old URLs&nbsp;<em>should</em>&nbsp;continue to work (and verify they actually do), and ship. Iterate from there.</p><p>One closing ask of ServiceNow, while I'm here. Lit, Tailwind, and DaisyUI are genuinely good choices. Lit ships releases a couple of times a year. Tailwind ships breaking changes. DaisyUI is already on v5. Please keep this stack current. Service Portal sat on AngularJS for&nbsp;<em>ten years</em>&nbsp;past the point Google started telling people to migrate. If the next time someone has to write a post like this is Knowledge 2036 because the framework underneath has fossilized again, the migration calculus is going to look very different. Pick a refresh cadence that's shorter than the lifespan of a Toyota Corolla.</p><p>Ten years on, I still think the framework rewards curiosity. The plane ride home from Knowledge has become a tradition. I'd like to keep it.</p><hr><h2 id="references">References</h2><ul><li>The runtime bundle:&nbsp;<code>/sncapps/aix/assets/index-D24AXy2E.js</code>&nbsp;(~2.4 MB — contains the URL pattern, route context, experience loader, and DaisyUI styles)</li><li>The experience config endpoint:&nbsp;<code>GET /api/now/aix/config/&lt;url_suffix&gt;</code>&nbsp;(Accept:&nbsp;<code>multipart/mixed</code>)</li><li>The 29-table list:&nbsp;<code>sys_db_object_list.do?sysparm_query=nameSTARTSWITHsys_aix</code></li><li>Five tables to read first:&nbsp;<code>sys_aix_experience</code>,&nbsp;<code>sys_aix_page</code>,&nbsp;<code>sys_aix_widget</code>,&nbsp;<code>sys_aix_widget_instance</code>,&nbsp;<code>sys_aix_url_rewrite_rule</code></li><li>The OOB widget to read first:&nbsp;<strong>Form</strong>&nbsp;(<code>form-widget</code>) — the canonical SP-bridge pattern in twenty lines</li></ul> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ What&#x27;s the best ServiceNow certification to get? ]]></title>
        <description><![CDATA[ Simplifying ServiceNow&#39;s certification hierarchy


The Problem Every Partner Knows

If you&#39;re a ServiceNow partner, you&#39;ve probably had this experience: you&#39;re trying to figure out what certifications your team needs to earn a specific Product Line Achievement, or what&#39;s actually in ]]></description>
        <link>https://relay.semaphorepartners.com/articles/whats-the-best-servicenow-certification-to-get/</link>
        <guid isPermaLink="false">69e0db0ff88c3a0001c620f2</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Mon, 20 Apr 2026 11:18:24 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/04/What-s-the-best-ServiceNow-certification-to-get_.png" medium="image"/>
        <content:encoded><![CDATA[ <p><em>Simplifying ServiceNow's certification hierarchy</em></p><h2 id="the-problem-every-partner-knows">The Problem Every Partner Knows</h2><p>If you're a ServiceNow partner, you've probably had this experience: you're trying to figure out what certifications your team needs to earn a specific Product Line Achievement, or what's actually <em>in</em> a Suite Certification, and you end up in a maze of clicks on the Now Learning portal that never quite gives you a straight answer.</p><p>"Suite Certification — CSM Professional." Great. What does that require? Click. Click. Click. Scroll. Open a new tab. Cross-reference with the partner guide PDF from last quarter. Wonder if it's been updated. Give up and Slack someone who already figured it out.</p><p>It shouldn't be this hard.</p><h2 id="what-we-were-trying-to-do">What We Were Trying to Do</h2><p>At Semaphore, we're a Specialist partner. We're always thinking about our certification strategy — which certs unlock which PLAs, where the overlap is, and where we can get the most coverage with the least duplication of effort across our team.</p><p>The problem is that ServiceNow's certification data is fragmented across multiple places. The Now Learning portal shows individual achievements, but it doesn't give you a clean view of the <em>hierarchy</em> — which suite certifications are composed of which underlying certs, which of those underlying certs are themselves suite certifications (yes, they nest), and which individual micro-certs or CIS exams are shared across multiple suites.</p><p>That last part is the real strategic question. If a single CIS exam is required by eight different suite certifications, that's a high-leverage cert to push your team toward. But you can't see that from the portal UI.</p><h2 id="what-the-data-reveals">What the Data Reveals</h2><p>There are currently <strong>30 active Suite Certifications</strong> in the partner program, requiring a combined pool of about <strong>50 unique individual achievements</strong> (CIS exams, Micro-Certifications, Accreditations, and the CSA). That's around 115 total requirement connections.</p><p>A few things jumped out immediately.</p><p><strong>The hierarchy goes deeper than you'd expect.</strong> The ITOM stack chains <em>four levels deep</em>: </p><ul><li>ITOM AIOps Enterprise Plus requires <ul><li>ITOM AIOps Enterprise, which requires <ul><li>ITOM AIOps Pro, which requires <ul><li>ITOM Visibility — which itself requires <ul><li>CIS-Discovery</li><li>CIS-Service Mapping</li><li>Agent Client Collector accreditation. </li></ul></li></ul></li></ul></li></ul></li></ul><p>If you're planning your ITOM certification path, you need to understand this full chain, not just the top-level suite.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/04/image.png" class="kg-image" alt="" loading="lazy" width="1000" height="1000" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2026/04/image.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/04/image.png 1000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">ITOM AIOps Enterprise Plus, ITOM AIOps Enterprise, ITOM AIOps Pro, ITOM Visibility etc...</span></figcaption></figure><p><strong>Shared requirements are everywhere — and they're strategic.</strong> </p><p>The CIS – Customer Service Management certification is required by eight different suites: </p><ol><li>CSM Professional</li><li>FSO Banking</li><li>FSO Insurance</li><li>Healthcare &amp; Life Sciences</li><li>Public Sector Digital Services</li><li>Retail</li><li>TMT Service Management</li><li>Order Management. </li></ol><p>If you have a consultant with CIS-CSM, they're one or two additional certs away from <em>any</em> of those suites.</p><p>Similarly, the trio of MC - Predictive Intelligence, MC - Performance Analytics, and MC - Virtual Agent shows up in the majority of Professional-tier suites. Get those three done and you've covered a massive chunk of the suite map.</p><p><strong>The Now Assist suites are thin wrappers.</strong> Each Now Assist Pro Plus suite (ITSM, CSM, HRSD) requires only two things: the corresponding Professional suite certification plus a Now Assist delivery accreditation. That's it. The Professional suite does the heavy lifting underneath.</p><p><strong>The CSA is the operational gateway.</strong> Six suites require the Certified System Administrator: Workflow Data Fabric, Workplace Service Delivery, Legal Service Delivery, Supplier Lifecycle, Sourcing &amp; Procurement, and Accounts Payable. For any consultant working in those areas, the CSA is table stakes.</p><h2 id="we-built-an-interactive-tool">We Built an Interactive Tool</h2><p>Staring at JSON and spreadsheets only gets you so far. We wanted a way to actually <em>see</em> the relationships — which certs feed into which suites, where the overlaps are, and which achievements give you the most bang for your buck.</p><p>So we built an interactive dependency wheel that visualizes the entire suite certification hierarchy: <a href="https://certifications.semaphorepartners.com/?ref=relay.semaphorepartners.com">https://certifications.semaphorepartners.com/</a></p><figure class="kg-card kg-image-card"><a href="https://certifications.semaphorepartners.com/?ref=relay.semaphorepartners.com"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/04/image-1.png" class="kg-image" alt="" loading="lazy" width="1238" height="1132" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2026/04/image-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2026/04/image-1.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/04/image-1.png 1238w" sizes="(min-width: 720px) 720px"></a></figure><p>Each arc around the wheel represents a certification, sized proportionally to the number of requirements it has or the number of suites that require it. Tap any arc to highlight its connections — blue lines show what it requires, orange lines show what requires it. The center displays the selected cert with its connection counts.</p><p>The color coding makes the structure immediately visible: blue for suite certifications, orange for CIS exams, green for micro-certifications, purple for accreditations, and teal for the CSA.</p><h2 id="why-this-matters-for-partners">Why This Matters for Partners</h2><p>This isn't just an academic exercise. For a boutique firm like ours, every certification investment has to be deliberate. Training hours cost real money, and we need to be strategic about which certs we pursue and who we assign them to.</p><p>Here's what this analysis told us about prioritization:</p><p><strong>Highest leverage CIS exam:</strong> CIS-CSM, by a wide margin. Eight suites. No other CIS exam comes close.</p><p><strong>Highest leverage micro-certs:</strong> The Predictive Intelligence / Performance Analytics / Virtual Agent trio. They're shared across all the Professional-tier suites.</p><p><strong>Most technically demanding micro-cert:</strong> Integration Hub. Required by seven operational suites plus Workflow Data Fabric. It's also the one that actually tests your ability to build integrations — scripted REST, Flow Designer actions, IntegrationHub spokes.</p><p><strong>Best entry point:</strong> CSA. It's the prerequisite for six suites and is functionally the entry ticket to the certification ecosystem.</p><h2 id="whats-next">What's Next</h2><p>If there's enough interest in this, we're planning to extend the tool to map certifications directly to Product Line Achievements — so you can answer the question "what does my team need to earn PLA in ITSM?" in one click instead of cross-referencing three different documents.</p><p>We've also added direct links to each achievement on the Now Learning portal, so the tool becomes a practical navigation layer on top of ServiceNow's own certification platform.</p><p>If you're a ServiceNow partner struggling with the same certification planning challenges, or if you just want to play with the interactive visualization, we'd love to hear from you. Drop us a line or check out the tool here: <a href="https://certifications.semaphorepartners.com/?ref=relay.semaphorepartners.com">https://certifications.semaphorepartners.com/</a>.</p><hr><p><em>Semaphore Partners is a boutique ServiceNow consulting firm specializing in ITSM, HRSD, CSM, SPM, and custom integrations. We're a 2026 ServiceNow Partner of the Year — Rising Star Americas recipient with a 5.0 CSAT rating. </em><a href="https://semaphorepartners.com/?ref=relay.semaphorepartners.com"><em>Learn more at semaphorepartners.com</em></a><em>.</em></p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ ServiceNow &amp; Microsoft Intune&#x27;s Service Graph Connector integration returning blank Assigned To values ]]></title>
        <description><![CDATA[ We recently ran into a frustrating issue with the ServiceNow Service Graph Connector for Microsoft Intune. Devices were syncing into the CMDB just fine, but the Assigned To field on cmdb_ci_handheld_computing records was coming up empty every time.

The first instinct is to check the mapping. In ]]></description>
        <link>https://relay.semaphorepartners.com/articles/servicenow-microsoft-intunes-service-graph-connector-integration-returning-blank-assigned-to-values/</link>
        <guid isPermaLink="false">69c29b0c9e1f7e000181370f</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Tue, 24 Mar 2026 12:47:31 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/03/intune_sgc_icons.svg" medium="image"/>
        <content:encoded><![CDATA[ <p>We recently ran into a frustrating issue with the ServiceNow Service Graph Connector for Microsoft Intune. Devices were syncing into the CMDB just fine, but the <strong>Assigned To</strong> field on <code>cmdb_ci_handheld_computing</code> records was coming up empty every time.</p><p>The first instinct is to check the mapping. In Integration Hub ETL (<code>$cmdb_integration_studio.do</code>), the <code>Assigned To</code> field was configured correctly: a User Lookup transform using <code>u_userdisplayname</code> as the User Name and <code>u_userprincipalname</code> as the Email, outputting to <code>assigned_to_user</code>. Nothing wrong there.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/03/image-2.png" class="kg-image" alt="" loading="lazy" width="905" height="781" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2026/03/image-2.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/03/image-2.png 905w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/03/image-1.png" class="kg-image" alt="" loading="lazy" width="1298" height="924" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2026/03/image-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2026/03/image-1.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/03/image-1.png 1298w" sizes="(min-width: 720px) 720px"></figure><p>So the problem must have been upstream. We opened <strong>Workflow Studio</strong> and looked at the <code>mobiledevices</code> Data Stream action. The Script Parser step (step 4) showed exactly what was being pulled from the Intune API response:</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/03/image.png" class="kg-image" alt="" loading="lazy" width="1314" height="781" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2026/03/image.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2026/03/image.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/03/image.png 1314w" sizes="(min-width: 720px) 720px"></figure><pre><code class="language-javascript">outputs.targetObject.userId = record.userId;
outputs.targetObject.userDisplayName = record.userDisplayName;
outputs.targetObject.userPrincipalName = record.userPrincipalName;</code></pre><p>Worth noting: the Service Graph Connector documentation doesn't explicitly tell you which fields are being used in the mapping. Knowing to look here, and knowing what to look <em>for</em>, comes from understanding how the ETL pipeline works end to end. The connector is a black box until you know where to crack it open.</p><p>When we checked the import set table (<code>sn_intune_integrat_devices</code>), the picture became clear. The <code>userId</code> field was populating, the Microsoft GUID for the user was coming through, but <code>userDisplayName</code> and <code>userPrincipalName</code> were both blank. This is a pattern we've seen before in ServiceNow: an account can resolve a record's GUID because it has basic read access to the object, but can't retrieve dot-walked attributes because it lacks permission on the related resource. In other words, the ID comes through; the human-readable fields don't.</p><p>Our hypothesis was an OAuth permissions gap. When we checked the <a href="https://learn.microsoft.com/en-us/graph/api/intune-devices-manageddevice-get?view=graph-rest-1.0&ref=relay.semaphorepartners.com#permissions">Microsoft Graph API documentation</a> for the managed device endpoint, it only lists two required permissions:</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/03/image-3.png" class="kg-image" alt="" loading="lazy" width="821" height="244" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2026/03/image-3.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/03/image-3.png 821w" sizes="(min-width: 720px) 720px"></figure><ul><li><code>DeviceManagementManagedDevices.Read.All</code></li><li><code>DeviceManagementManagedDevices.ReadWrite.All</code></li></ul><p>No mention of user permissions. If you stop there, you'd think you were covered. But the <a href="https://www.servicenow.com/docs/r/zurich/servicenow-platform/service-graph-connectors/sgcc-configure-intune-integ.html?ref=relay.semaphorepartners.com">ServiceNow documentation for configuring the Intune Service Graph Connector</a> specifies a third permission that the App Registration needs:</p><ul><li><code>DeviceManagementManagedDevices.Read.All</code> (Application)</li><li><code>DeviceManagementApps.Read.All</code> (Application)</li><li><strong><code>User.Read.All</code> (Application)</strong></li></ul><p>That last one was missing. Without <code>User.Read.All</code>, the Graph API returns the device record with the user's object ID intact but won't expand any user attributes. No error, no warning, just silently empty fields.</p><p>Once we added <code>User.Read.All</code> to the App Registration in the Microsoft portal and triggered a fresh sync, <code>userDisplayName</code> and <code>userPrincipalName</code> started populating in the import set table, the User Lookup transform fired correctly, and <strong>Assigned To</strong> resolved properly on the CMDB records. Problem solved.</p><p>If you're seeing the same thing, devices syncing but <strong>Assigned To</strong> is still blank, skip straight to your Azure App Registration and verify all three permissions are present. The Microsoft docs alone won't get you there.</p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Embedding reusable custom widgets in catalog items ]]></title>
        <description><![CDATA[ Level up your request forms with advanced custom inputs and components. ]]></description>
        <link>https://relay.semaphorepartners.com/articles/embedding-reusable-custom-widgets-in-catalog-items/</link>
        <guid isPermaLink="false">699f64cf1d6a67000198ea16</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ James McGaha ]]></dc:creator>
        <pubDate>Wed, 04 Mar 2026 09:20:26 -0500</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2026/02/custom-widgets-in-catalog-forms.png" medium="image"/>
        <content:encoded><![CDATA[ <p>In general, the best catalog items are simple, straightforward, and easy to maintain. Adding custom embedded widgets to a catalog form can often make support and future enhancements more complicated and confusing. However, when used correctly, reusable custom widgets can add significant value across your whole catalog while introducing minimal additional complexity.</p><p>For example, if you have many catalog items with a shipping address section, you could create a single, configurable widget for a typeahead address input with API validation. A widget like that could greatly improve both the user experience and the quality of the provided address data. Additionally, if built correctly, adding in the upgraded, custom input to a form can be as easy as adding one new variable record without requiring any changes to the existing form logic.</p><p>In this post, I'll walk through three examples of reusable catalog item widgets which cover what I consider to be the most effective patterns and the most common use-cases. For each example, I've included screenshots and the code for the widget. Additionally, if you want to see the widgets in action, you can download&nbsp;<a href="https://developer.servicenow.com/connect.do?ref=relay.semaphorepartners.com#!/share/contents/2146901_reusable_custom_widgets_for_catalog_items1?t=PRODUCT_DETAILS" rel="noreferrer">Reusable Custom Widgets for Catalog Items</a>&nbsp;from ServiceNow Share where I've added these widgets to an example catalog item.</p><h2 id="example-1-loading-indicators">Example 1: Loading Indicators</h2>
<!--kg-card-begin: html-->
<style>
.code-tabs {
    margin: 1.5rem 0;
    border-radius: 8px;
    overflow: hidden;
    border: 1px solid #334155;
}
.tab-buttons {
    display: flex;
    background: #1e293b;
    border-bottom: 1px solid #334155;
}
.tab-btn {
    background: transparent;
    border: none;
    border-right: 1px solid #334155;
    color: #94a3b8;
    padding: 0.35rem 0.9rem;
    cursor: pointer;
    font-size: 0.8rem;
    font-weight: 500;
    font-family: inherit;
    transition: color 0.2s, background 0.2s;
}
.tab-btn:hover {
    color: #e2e8f0;
    background: rgba(255,255,255,0.06);
}
.tab-btn.active {
    color: #e2e8f0;
    background: rgba(102,126,234,0.15);
    border-bottom: 2px solid #667eea;
}
.tab-panel { display: none; }
.tab-panel.active { display: block; }

/* Light panel (screenshot / text) */
.tab-panel-light {
    background: #f8fafc;
    padding: 0.75rem 1rem;
    color: #4a5568;
}
.tab-panel-light img {
    max-height: 336px;
    max-width: 100%;
    display: block;
    margin: 0 auto;
    border-radius: 6px;
    border: 1px solid #e2e8f0;
}
/* Dark code panel */
.tab-panel-code {
    background: #1e293b;
    position: relative;
}
.tab-panel-code pre {
    font-size: 12px;
    margin: 0;
    border: none;
    border-radius: 0;
    max-height: 360px;
    overflow-y: auto;
    padding: 0.75rem;
    background: transparent;
    color: #e2e8f0;
    white-space: pre-wrap;
    word-wrap: break-word;
}
.tab-panel-code pre code {
    background: transparent;
    padding: 0;
    color: inherit;
    font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}
</style>


<div class="code-tabs">
    <div class="tab-buttons">
        <button class="tab-btn active"  onclick="switchTab(this,'tabs-0-0')">Screenshot</button>
        <button class="tab-btn"         onclick="switchTab(this,'tabs-0-1')">HTML</button>
        <button class="tab-btn"         onclick="switchTab(this,'tabs-0-2')">Client Script</button>
 	<button class="tab-btn"         onclick="switchTab(this,'tabs-0-3')">CSS</button>
    </div>
    <div class="tab-panels">
        <div id="tabs-0-0" class="tab-panel tab-panel-light active">
            <img src="https://www.supernow-blog.com/images/loading-indicators-widget.png" alt="Screenshot" />
        </div>
        <div id="tabs-0-1" class="tab-panel tab-panel-code">
           <pre class="language-html" tabindex="0"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>supernow-loading-field<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>sp-loading-indicator<span class="token punctuation">"</span></span> <span class="token attr-name">ng-class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">'</span>v'+widget.sys_id<span class="token punctuation">"</span></span> <span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>display: none;<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>supernow-loading-block-skeleton<span class="token punctuation">"</span></span> <span class="token attr-name">ng-class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">'</span>v'+widget.sys_id<span class="token punctuation">"</span></span> <span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>display: none<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>supernow-loading-block-skeleton<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>loading card-title shorter-s<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>loading table-content<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>loading card-title shorter-m<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>loading table-content<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>loading card-title shorter-xs<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>loading table-content<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>supernow-loading-block-loader<span class="token punctuation">"</span></span> <span class="token attr-name">ng-class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">'</span>v'+widget.sys_id<span class="token punctuation">"</span></span> <span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>display: none<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>supernow-mug-container<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>supernow-mug<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>supernow-coffee<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>loader-text<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        Loading . . .
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>supernow-loading-overlay<span class="token punctuation">"</span></span> <span class="token attr-name">ng-class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">'</span>v'+widget.sys_id<span class="token punctuation">"</span></span> <span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>display: none<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>supernow-loading-overlay-inner<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>supernow-mug-container<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>supernow-mug<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>supernow-coffee<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>supernow-loading-overlay-text<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>loader-text<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        Loading . . .
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">&gt;</span></span>
  #catItemTop &gt; * {
    position: relative;
  }
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">&gt;</span></span>
</code></pre>
           
        </div>
        <div id="tabs-0-2" class="tab-panel tab-panel-code">
<pre class="language-javascript" tabindex="0"><code class="language-javascript">api<span class="token punctuation">.</span><span class="token function-variable function">controller</span><span class="token operator">=</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">$scope<span class="token punctuation">,</span> $rootScope</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">var</span> c <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">;</span>
	c<span class="token punctuation">.</span>activeLoaders <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
	c<span class="token punctuation">.</span>madeReadOnlyFields <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
	c<span class="token punctuation">.</span>movedOverlay <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>

	<span class="token comment">/*
		PROPERTIES:
		type
			Description: the type of loader to show (small loading icon next to a field's label, a placeholder loader to indicate field will soon be shown in that area, a loader showing below a field on the form along with loading text, or a whole-form overlay loader that will prevent interacting with the form while loading)
			Value: "field", "block-skeleton", "block-loader", or "overlay"
			Default: "overlay"
		action
			Description: whether to hide or show the loader specified
			Value: "show" or "hide"
			Default: "show" (unless a matching loader already exists and is displayed)
			Applies for type: All
		text: 
			Description: the text to show below the loading animation
			Value: any string
			Default: "Loading . . ."
			Applies for type: "block-loader" and "overlay"
		field:
			Description: the field to attach the loader to or below
			Value: the name of any variable in the catalog item
			Default: the variable embedding this widget
			Applies for type: "field", "block-skeleton", and "block-loader"
		disableField:
			Description: whether the corresponding field should be made read-only while loading
			Value: true or false
			Default: true
			Applies for type: "field", "block-skeleton", and "block-loader"
			
		EXAMPLES:
		{"type": "field", "field": "test", "disableField": false}
		{"type": "block-loader", "field": "test", "action": "show"}
		{"text": "Loading form data . . . ""}
	*/</span>
	<span class="token keyword">var</span> fieldChangeListener <span class="token operator">=</span> $rootScope<span class="token punctuation">.</span><span class="token function">$on</span><span class="token punctuation">(</span><span class="token string">"field.change."</span><span class="token operator">+</span>$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>field<span class="token punctuation">.</span>name<span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">evt<span class="token punctuation">,</span> parms</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
		<span class="token keyword">if</span><span class="token punctuation">(</span>parms<span class="token punctuation">.</span>newValue<span class="token punctuation">)</span><span class="token punctuation">{</span>
			<span class="token keyword">try</span><span class="token punctuation">{</span>
				<span class="token keyword">var</span> val <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>parms<span class="token punctuation">.</span>newValue<span class="token punctuation">)</span><span class="token punctuation">;</span>
				<span class="token keyword">var</span> show <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
				<span class="token keyword">var</span> el<span class="token punctuation">;</span>
				<span class="token keyword">var</span> textElement<span class="token punctuation">;</span>
				<span class="token keyword">if</span><span class="token punctuation">(</span>val<span class="token punctuation">.</span>type <span class="token operator">==</span> <span class="token string">'field'</span> <span class="token operator">||</span> val<span class="token punctuation">.</span>type <span class="token operator">==</span> <span class="token string">'block-skeleton'</span> <span class="token operator">||</span> val<span class="token punctuation">.</span>type <span class="token operator">==</span> <span class="token string">'block-loader'</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
					<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>val<span class="token punctuation">.</span>field<span class="token punctuation">)</span> val<span class="token punctuation">.</span>field <span class="token operator">=</span> $scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>field<span class="token punctuation">.</span>name<span class="token punctuation">;</span>
					el <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">"supernow-loading-"</span><span class="token operator">+</span>val<span class="token punctuation">.</span>type<span class="token operator">+</span><span class="token string">"-"</span><span class="token operator">+</span>val<span class="token punctuation">.</span>field<span class="token punctuation">)</span><span class="token punctuation">;</span>
					<span class="token keyword">if</span><span class="token punctuation">(</span>val<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span><span class="token string">'action'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
						show <span class="token operator">=</span> val<span class="token punctuation">.</span>action <span class="token operator">==</span> <span class="token string">'show'</span><span class="token punctuation">;</span>
					<span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span><span class="token punctuation">{</span>
						show <span class="token operator">=</span> el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>display <span class="token operator">==</span> <span class="token string">'none'</span><span class="token punctuation">;</span>
					<span class="token punctuation">}</span>
					<span class="token keyword">if</span><span class="token punctuation">(</span>show<span class="token punctuation">)</span><span class="token punctuation">{</span>
						<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>el<span class="token punctuation">)</span><span class="token punctuation">{</span>
							<span class="token keyword">var</span> p <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">"supernow-loading-"</span><span class="token operator">+</span>val<span class="token punctuation">.</span>type<span class="token punctuation">)</span><span class="token punctuation">;</span>
							el <span class="token operator">=</span> p<span class="token punctuation">.</span><span class="token function">cloneNode</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
							el<span class="token punctuation">.</span>id <span class="token operator">=</span> <span class="token string">"supernow-loading-"</span><span class="token operator">+</span>val<span class="token punctuation">.</span>type<span class="token operator">+</span><span class="token string">"-"</span><span class="token operator">+</span>val<span class="token punctuation">.</span>field<span class="token punctuation">;</span>
							<span class="token keyword">var</span> fieldLabel <span class="token operator">=</span> val<span class="token punctuation">.</span>type <span class="token operator">==</span> <span class="token string">'field'</span> <span class="token operator">?</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">'#'</span><span class="token operator">+</span>val<span class="token punctuation">.</span>field<span class="token operator">+</span><span class="token string">' label.field-label'</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">'#'</span><span class="token operator">+</span>val<span class="token punctuation">.</span>field<span class="token operator">+</span><span class="token string">':has(.form-group)'</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
							fieldLabel<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span><span class="token punctuation">;</span>
						<span class="token punctuation">}</span>
						<span class="token keyword">if</span><span class="token punctuation">(</span>val<span class="token punctuation">.</span>type <span class="token operator">==</span> <span class="token string">'block-loader'</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
							textElement <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">'#supernow-loading-block-loader-'</span><span class="token operator">+</span>val<span class="token punctuation">.</span>field<span class="token operator">+</span><span class="token string">' .loader-text'</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
							textElement<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> val<span class="token punctuation">.</span>text <span class="token operator">?</span> val<span class="token punctuation">.</span>text <span class="token operator">:</span> <span class="token string">'Loading . . .'</span><span class="token punctuation">;</span>
						<span class="token punctuation">}</span>
						el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>display <span class="token operator">=</span> val<span class="token punctuation">.</span>type <span class="token operator">==</span> <span class="token string">'field'</span> <span class="token operator">?</span> <span class="token string">'inline-flex'</span> <span class="token operator">:</span> <span class="token string">'block'</span><span class="token punctuation">;</span>
						<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>val<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span><span class="token string">'disableField'</span><span class="token punctuation">)</span> <span class="token operator">||</span> val<span class="token punctuation">.</span>disableField <span class="token operator">===</span> <span class="token boolean">true</span> <span class="token operator">||</span> val<span class="token punctuation">.</span>disableField <span class="token operator">===</span> <span class="token string">'true'</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
							<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">isReadOnly</span><span class="token punctuation">(</span>val<span class="token punctuation">.</span>field<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
								c<span class="token punctuation">.</span>madeReadOnlyFields<span class="token punctuation">[</span>val<span class="token punctuation">.</span>field<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
								$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">setReadOnly</span><span class="token punctuation">(</span>val<span class="token punctuation">.</span>field<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
							<span class="token punctuation">}</span>
						<span class="token punctuation">}</span>
						c<span class="token punctuation">.</span>activeLoaders<span class="token punctuation">[</span>val<span class="token punctuation">.</span>type<span class="token operator">+</span><span class="token string">'-'</span><span class="token operator">+</span>val<span class="token punctuation">.</span>field<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
					<span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>
						el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>display <span class="token operator">=</span> <span class="token string">'none'</span><span class="token punctuation">;</span>
						<span class="token keyword">if</span><span class="token punctuation">(</span>val<span class="token punctuation">.</span>type <span class="token operator">==</span> <span class="token string">'block-loader'</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
							textElement <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">'#supernow-loading-block-loader-'</span><span class="token operator">+</span>val<span class="token punctuation">.</span>field<span class="token operator">+</span><span class="token string">' .loader-text'</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
							textElement<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>	
						<span class="token punctuation">}</span> 
						<span class="token keyword">if</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>madeReadOnlyFields<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span>val<span class="token punctuation">.</span>field<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
							<span class="token keyword">delete</span> c<span class="token punctuation">.</span>madeReadOnlyFields<span class="token punctuation">[</span>val<span class="token punctuation">.</span>field<span class="token punctuation">]</span><span class="token punctuation">;</span>
							$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">setReadOnly</span><span class="token punctuation">(</span>val<span class="token punctuation">.</span>field<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
						<span class="token punctuation">}</span>
						<span class="token keyword">delete</span> c<span class="token punctuation">.</span>activeLoaders<span class="token punctuation">[</span>val<span class="token punctuation">.</span>type<span class="token operator">+</span><span class="token string">'-'</span><span class="token operator">+</span>val<span class="token punctuation">.</span>field<span class="token punctuation">]</span><span class="token punctuation">;</span>
					<span class="token punctuation">}</span>
				<span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>
					el <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'supernow-loading-overlay'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
					textElement <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'supernow-loading-overlay-text'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
					<span class="token keyword">if</span><span class="token punctuation">(</span>val<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span><span class="token string">'action'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
						show <span class="token operator">=</span> val<span class="token punctuation">.</span>action <span class="token operator">==</span> <span class="token string">'show'</span><span class="token punctuation">;</span>
					<span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span><span class="token punctuation">{</span>
						show <span class="token operator">=</span> el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>display <span class="token operator">==</span> <span class="token string">'none'</span><span class="token punctuation">;</span>
					<span class="token punctuation">}</span>
					<span class="token keyword">if</span><span class="token punctuation">(</span>show<span class="token punctuation">)</span><span class="token punctuation">{</span>
						<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>c<span class="token punctuation">.</span>movedOverlay<span class="token punctuation">)</span><span class="token punctuation">{</span>
							<span class="token keyword">var</span> catItem <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'catalog-form'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
							catItem<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span><span class="token punctuation">;</span>
						<span class="token punctuation">}</span>
						textElement<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> val<span class="token punctuation">.</span>text <span class="token operator">?</span> val<span class="token punctuation">.</span>text <span class="token operator">:</span> <span class="token string">'Loading . . .'</span><span class="token punctuation">;</span>
						el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>display <span class="token operator">=</span> <span class="token string">'block'</span><span class="token punctuation">;</span>
						c<span class="token punctuation">.</span>activeLoaders<span class="token punctuation">.</span>overlay <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
					<span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>
						el<span class="token punctuation">.</span>style<span class="token punctuation">.</span>display <span class="token operator">=</span> <span class="token string">'none'</span><span class="token punctuation">;</span>
						textElement<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>
						<span class="token keyword">delete</span> c<span class="token punctuation">.</span>activeLoaders<span class="token punctuation">.</span>overlay<span class="token punctuation">;</span>
					<span class="token punctuation">}</span>
				<span class="token punctuation">}</span>
			<span class="token punctuation">}</span><span class="token keyword">catch</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">{</span>
				console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Could not set a loading indicator using the value: '</span><span class="token operator">+</span>parms<span class="token punctuation">.</span>newValue<span class="token operator">+</span><span class="token string">'\n\nEnsure that the value you are setting is valid JSON with double quotes around both property names and string values'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token punctuation">}</span>
			$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">clearValue</span><span class="token punctuation">(</span>$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>field<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span>
	<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	$scope<span class="token punctuation">.</span><span class="token function">$on</span><span class="token punctuation">(</span><span class="token string">"$destroy"</span><span class="token punctuation">,</span> fieldChangeListener<span class="token punctuation">)</span><span class="token punctuation">;</span>

	$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span>$<span class="token keyword">private</span><span class="token punctuation">.</span>events<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'onSubmit'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
		<span class="token keyword">if</span><span class="token punctuation">(</span>Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>activeLoaders<span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">{</span>
			$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">addErrorMessage</span><span class="token punctuation">(</span><span class="token string">'Please wait for the form to finish loading'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span>
	<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>	
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
        </div>
 	<div id="tabs-0-3" class="tab-panel tab-panel-code">
<pre class="language-css" tabindex="0"><code class="language-css"><span class="token comment">/* block-skeleton */</span>
<span class="token selector">.supernow-loading-block-skeleton .card-title.loading</span> <span class="token punctuation">{</span>
  <span class="token property">margin-bottom</span><span class="token punctuation">:</span> 5px<span class="token punctuation">;</span>
  <span class="token property">height</span><span class="token punctuation">:</span> 14px<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.supernow-loading-block-skeleton .card-title.loading.shorter-s</span><span class="token punctuation">{</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 25%<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.supernow-loading-block-skeleton .card-title.loading.shorter-m</span><span class="token punctuation">{</span>
  <span class="token property">margin-top</span><span class="token punctuation">:</span> 21px<span class="token punctuation">;</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 30%<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.supernow-loading-block-skeleton .card-title.loading.shorter-xs</span><span class="token punctuation">{</span>
  <span class="token property">margin-top</span><span class="token punctuation">:</span> 21px<span class="token punctuation">;</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 20%<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.supernow-loading-block-skeleton .loading</span> <span class="token punctuation">{</span>
  <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span>
  <span class="token property">background-color</span><span class="token punctuation">:</span> #f1f1f1<span class="token punctuation">;</span>
  <span class="token property">overflow</span><span class="token punctuation">:</span> hidden<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.supernow-loading-block-skeleton .table-content.loading</span> <span class="token punctuation">{</span>
  <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span>
  <span class="token property">height</span><span class="token punctuation">:</span> 34px<span class="token punctuation">;</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.supernow-loading-block-skeleton .loading::after</span> <span class="token punctuation">{</span>
  <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span>
  <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span>
  <span class="token property">animation</span><span class="token punctuation">:</span> 2s supernow-block-loading linear 0.5s infinite<span class="token punctuation">;</span>
  <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>90deg<span class="token punctuation">,</span> transparent<span class="token punctuation">,</span> #e6e6e6<span class="token punctuation">,</span> transparent<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token property">bottom</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token property">left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token property">right</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token property">top</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token atrule"><span class="token rule">@keyframes</span> supernow-block-loading</span> <span class="token punctuation">{</span>
  <span class="token selector">0%</span> <span class="token punctuation">{</span>
    <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateX</span><span class="token punctuation">(</span>-100%<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">60%</span> <span class="token punctuation">{</span>
    <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateX</span><span class="token punctuation">(</span>100%<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">100%</span> <span class="token punctuation">{</span>
    <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateX</span><span class="token punctuation">(</span>100%<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">/* overlay */</span> 
<span class="token selector">#supernow-loading-overlay-inner</span> <span class="token punctuation">{</span>
  <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span>
  <span class="token property">top</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token property">left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
  <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
  <span class="token property">z-index</span><span class="token punctuation">:</span> 19<span class="token punctuation">;</span>
  <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.1<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token property">backdrop-filter</span><span class="token punctuation">:</span> <span class="token function">blur</span><span class="token punctuation">(</span>2px<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">/* coffee mug for overlay and block-loader */</span>
<span class="token selector">.supernow-mug</span> <span class="token punctuation">{</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 40px<span class="token punctuation">;</span>
  <span class="token property">height</span><span class="token punctuation">:</span> 45px<span class="token punctuation">;</span>
  <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span>
  <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span>
  <span class="token property">border</span><span class="token punctuation">:</span> 4px solid #206AAA<span class="token punctuation">;</span>
  <span class="token property">animation</span><span class="token punctuation">:</span> fill 2s linear infinite alternate<span class="token punctuation">;</span>
  <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>#a05425<span class="token punctuation">,</span> 0.9<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token property">border-radius</span><span class="token punctuation">:</span> 0 0 4px 4px<span class="token punctuation">;</span>
  <span class="token property">z-index</span><span class="token punctuation">:</span> 5<span class="token punctuation">;</span>
  <span class="token selector">&amp;::after</span> <span class="token punctuation">{</span>
    <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">;</span>
    <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span>
    <span class="token property">left</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
    <span class="token property">top</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span>
    <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>-50%<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token property">border</span><span class="token punctuation">:</span> 4px solid #206AAA<span class="token punctuation">;</span>
    <span class="token property">width</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span>
    <span class="token property">height</span><span class="token punctuation">:</span> 25px<span class="token punctuation">;</span>
    <span class="token property">border-radius</span><span class="token punctuation">:</span> 0 8px 8px 0<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token selector">.supernow-coffee</span><span class="token punctuation">{</span>
  <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span>
  <span class="token property">left</span><span class="token punctuation">:</span> 2px<span class="token punctuation">;</span>
  <span class="token property">right</span><span class="token punctuation">:</span> 2px<span class="token punctuation">;</span>
  <span class="token property">bottom</span><span class="token punctuation">:</span> 2px<span class="token punctuation">;</span>
  <span class="token property">margin</span><span class="token punctuation">:</span> 2px<span class="token punctuation">;</span>
  <span class="token property">background</span><span class="token punctuation">:</span> #a05425<span class="token punctuation">;</span>
  <span class="token property">border-radius</span><span class="token punctuation">:</span> 0 0 6px 6px<span class="token punctuation">;</span>
  <span class="token property">animation</span><span class="token punctuation">:</span> supernow-coffee 4s ease-in-out infinite alternate-reverse<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token atrule"><span class="token rule">@keyframes</span> supernow-coffee</span> <span class="token punctuation">{</span>
  <span class="token selector">from</span> <span class="token punctuation">{</span>
    <span class="token property">top</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>100% - 35px<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">to</span> <span class="token punctuation">{</span>
    <span class="token property">top</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>100% - 7px<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token selector">.supernow-mug-container .loader-text</span> <span class="token punctuation">{</span>
  <span class="token property">font-size</span><span class="token punctuation">:</span> 1.3em<span class="token punctuation">;</span>
  <span class="token property">font-weight</span><span class="token punctuation">:</span> 600<span class="token punctuation">;</span>
  <span class="token property">color</span><span class="token punctuation">:</span> #206AAA<span class="token punctuation">;</span>
  <span class="token property">margin-top</span><span class="token punctuation">:</span> 15px<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.supernow-mug-container .loader-text</span> <span class="token punctuation">{</span>
  <span class="token property">color</span><span class="token punctuation">:</span> #002677<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.supernow-mug-container</span><span class="token punctuation">{</span>
  <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
  <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
  <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span>
  <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
  <span class="token property">justify-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
        </div>
    </div>
</div>

<script>
function switchTab(btn, panelId) {
    var tabs = btn.closest('.code-tabs');
    tabs.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });
    tabs.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('active'); });
    btn.classList.add('active');
    document.getElementById(panelId).classList.add('active');
}
</script>

<!--kg-card-end: html-->
<p>This example widget doesn't capture any user input; instead, catalog client scripts can trigger it to show various loading indicators on the form. One example where a widget like this would be useful is if a form includes a slow-running script or API call. In that scenario, you could see issues from the user modifying the values of other variables while the GlideAjax is still running and the data returned from the GlideAjax might no longer be relevant when it finally hits the callback. This widget allows you to make the loading more prominent or use an overlay to prevent user interaction with the form until loading completes.</p><h3 id="1-how-to-create-the-variable-for-embedding-the-widget"><u>1. How to create the variable for embedding the widget</u></h3><p>To add a widget to a form, you need a variable with:</p><ol><li>A Type value of Custom, Custom with Label, or Single Line Text.</li><li>A widget referenced in the Widget (sp_widget) field and/or a value in the Default Value field like&nbsp;<code>{widget: 'supernow-loading-indicators'}</code>.<ol><li><em>If using Single Line Text, the Widget field must have a value.</em></li><li><em>If both are specified and are different, the widget specified in the default value is used.</em></li></ol></li></ol><p>For this example, we don't want the widget variable itself to display or take up any space on the form. So, we use the Type "Custom" to not have a label included and we check the "Hidden" checkbox for the variable.</p><h3 id="2-how-to-trigger-the-widget-from-a-client-script"><u>2. How to trigger the widget from a client script</u></h3><p>The easiest way to communicate to and from a widget is to set values on the form and listen for those changes.</p><p>An embedded widget comes with built-in access to g_form through $scope.page.g_form (don't forget to inject $scope into your controller), which makes it easy to set values from the widget. To set up the widget to listen for changes to a variable's value, the most straightforward option is to inject $rootScope into the controller and set up a listener for the event name "field.change". The function attached to that listener will trigger on any variable's value changing, but if you want to listen just for changes to a specific variable, append that variable name to the event name like "field.change.variable_name".</p><p>In this example, a client script triggers the widget by setting the value of the variable that embeds the widget. So, it has a watcher like this:</p><pre><code class="language-javascript">var fieldChangeListener = $rootScope.$on("field.change."+$scope.page.field.name, function(evt, parms) {
   [ . . . ]
});
$scope.$on("$destroy", fieldChangeListener);
</code></pre><p><em>Notes:</em></p><ol><li><em>The widget can access the name of the variable it is embedded in by using&nbsp;<code>$scope.page.field.name</code>.</em></li><li><em>The changed variable's value is in the "newValue" property of the second parameter.</em></li><li><em>Important: when setting a watcher on $rootScope, ensure that you clear that watcher when the current scope is destroyed (as shown in the example above)</em></li></ol><p>This example expects a client script to set its variable's value to be an object containing the details of the loading indicator to be shown. The listener clears out the value of its variable so that the value set is treated like a "message", rather than maintaining the state of the loaders in the variable value itself.</p><h3 id="3-how-to-add-elements-elsewhere-in-the-form"><u>3. How to add elements elsewhere in the form</u></h3><p>In this example, we want to add loading indicators elsewhere in the form and not be limited to just displaying where the widget's variable is placed. It's easy to use JavaScript to clone, move, or create elements. However, be aware that moving an element preserves its AngularJS linkages to the controller, but cloning or creating an element does not. In this example, that distinction isn't important. However, if the loading elements needed access to the controller scope, you could use an ng-repeat to generate as many elements as needed and move those, instead of cloning and moving.</p><p>One other important thing to note is that all the selectors defined in the widget's CSS field will have a class selector of "v" plus the widget's sys_id prepended. This means that elements you move or create outside of the widget's element won't have the widget's CSS applied. For this example, this issue was avoided by manually appending a matching class to each element moved outside the widget and also just defining simple styling inline in the HTML.&nbsp;<em>Alternatively, you could create a &lt;style&gt; element in the HTML or add your styling to a portal-wide stylesheet, but with that approach it's important to keep your selectors very specific as those selectors will apply to the whole page or portal respectively.</em></p><h3 id="4-how-to-handle-onsubmit-validation"><u>4. How to handle onSubmit validation</u></h3><p>If your widget needs to perform some validation onSubmit, the officially recommended option would be to keep some hidden variable on the form up to date with whatever data is relevant for the onSubmit check (setting the value of that variable in the widget using&nbsp;<code>$scope.page.g_form.setValue</code>). This allows you to use a normal onSubmit catalog client script to perform validation using the data stored in the hidden variable.</p><p>However, that approach makes your reusable widget less "plug and play", as you'll need to add onSubmit client script logic to every form where your widget is included. So, in this example, we use an undocumented g_form event listener with the code:&nbsp;<code>$scope.page.g_form.$private.events.on('onSubmit', function(){ [ . . . ] });</code>. The specified function will work the same as an onSubmit client script, allowing you to block submission using&nbsp;<code>return false;</code>—except in this function you can access all the data from your widget's scope.</p><h2 id="example-2-time-picker">Example 2: Time Picker</h2>
<!--kg-card-begin: html-->
<div class="code-tabs">
    <div class="tab-buttons">
        <button class="tab-btn active"  onclick="switchTab(this,'tabs-1-0')">Screenshot</button>
        <button class="tab-btn"         onclick="switchTab(this,'tabs-1-1')">HTML</button>
        <button class="tab-btn"         onclick="switchTab(this,'tabs-1-2')">Client Script</button>
    </div>
    <div class="tab-panels">
        <div id="tabs-1-0" class="tab-panel tab-panel-light active">
            <img src="https://www.supernow-blog.com/images/time-picker-widget.png" />
        </div>
        <div id="tabs-1-1" class="tab-panel tab-panel-code">
           <pre class="language-html" tabindex="0"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">uib-timepicker</span> 
     <span class="token attr-name">ng-model</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c.selectedTime<span class="token punctuation">"</span></span> 
     <span class="token attr-name">ng-change</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c.storeValue()<span class="token punctuation">"</span></span> 
     <span class="token attr-name">ng-disabled</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>page.field.isReadonly()<span class="token punctuation">"</span></span>
     <span class="token attr-name">hour-step</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>options.hourStep<span class="token punctuation">"</span></span> 
     <span class="token attr-name">minute-step</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>options.minuteStep<span class="token punctuation">"</span></span> 
     <span class="token attr-name">show-seconds</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>options.showSeconds<span class="token punctuation">"</span></span> 
     <span class="token attr-name">second-step</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>options.secondStep<span class="token punctuation">"</span></span> 
     <span class="token attr-name">show-meridian</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>options.showMeridian<span class="token punctuation">"</span></span> 
     <span class="token attr-name">meridians</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>options.meridians<span class="token punctuation">"</span></span>
     <span class="token attr-name">show-spinners</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>options.showSpinners<span class="token punctuation">"</span></span> 
     <span class="token attr-name">mousewheel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>options.mousewheel<span class="token punctuation">"</span></span>
     <span class="token attr-name">arrowkeys</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>options.arrowkeys<span class="token punctuation">"</span></span> 
     <span class="token attr-name">readonly-input</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>options.readonlyInput<span class="token punctuation">"</span></span> 
     <span class="token attr-name">min</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>options.min<span class="token punctuation">"</span></span> 
     <span class="token attr-name">max</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>options.max<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span> 
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre>
           
        </div>
        <div id="tabs-1-2" class="tab-panel tab-panel-code">
<pre class="language-javascript" tabindex="0"><code class="language-javascript"> api<span class="token punctuation">.</span><span class="token function-variable function">controller</span><span class="token operator">=</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">$scope<span class="token punctuation">,</span> $rootScope</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">var</span> c <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">;</span>

	c<span class="token punctuation">.</span><span class="token function-variable function">dateFromString</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">string</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
		<span class="token keyword">if</span><span class="token punctuation">(</span>string<span class="token punctuation">)</span><span class="token punctuation">{</span>
			<span class="token keyword">var</span> timeParts <span class="token operator">=</span> string<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">':'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token keyword">var</span> hourPart <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>timeParts<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token keyword">if</span><span class="token punctuation">(</span>hourPart<span class="token punctuation">)</span><span class="token punctuation">{</span>
				<span class="token keyword">var</span> date <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
				date<span class="token punctuation">.</span><span class="token function">setHours</span><span class="token punctuation">(</span><span class="token function">parseInt</span><span class="token punctuation">(</span>timeParts<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
				<span class="token keyword">if</span><span class="token punctuation">(</span>timeParts<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">{</span> 
					date<span class="token punctuation">.</span><span class="token function">setMinutes</span><span class="token punctuation">(</span><span class="token function">parseInt</span><span class="token punctuation">(</span>timeParts<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
					<span class="token keyword">if</span><span class="token punctuation">(</span>timeParts<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
						date<span class="token punctuation">.</span><span class="token function">setSeconds</span><span class="token punctuation">(</span><span class="token function">parseInt</span><span class="token punctuation">(</span>timeParts<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
					<span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>
						date<span class="token punctuation">.</span><span class="token function">setSeconds</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
					<span class="token punctuation">}</span>
				<span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>
					data<span class="token punctuation">.</span><span class="token function">setMinutes</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
				<span class="token punctuation">}</span>
				<span class="token keyword">return</span> date<span class="token punctuation">;</span>
			<span class="token punctuation">}</span>
		<span class="token punctuation">}</span>
		<span class="token keyword">return</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span><span class="token punctuation">;</span>
	
	c<span class="token punctuation">.</span><span class="token function-variable function">stringFromDate</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">date</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
		<span class="token keyword">var</span> timeString <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>
		<span class="token keyword">if</span><span class="token punctuation">(</span>date<span class="token punctuation">)</span><span class="token punctuation">{</span>
			<span class="token keyword">var</span> hours <span class="token operator">=</span> date<span class="token punctuation">.</span><span class="token function">getHours</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token keyword">var</span> minutes <span class="token operator">=</span> date<span class="token punctuation">.</span><span class="token function">getMinutes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token keyword">var</span> seconds <span class="token operator">=</span> date<span class="token punctuation">.</span><span class="token function">getSeconds</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
			timeString <span class="token operator">+=</span> <span class="token punctuation">(</span>hours <span class="token operator">&lt;</span> <span class="token number">10</span> <span class="token operator">?</span> <span class="token string">"0"</span> <span class="token operator">+</span> hours <span class="token operator">:</span> hours<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">":"</span><span class="token punctuation">;</span>
			timeString <span class="token operator">+=</span> <span class="token punctuation">(</span>minutes <span class="token operator">&lt;</span> <span class="token number">10</span> <span class="token operator">?</span> <span class="token string">"0"</span> <span class="token operator">+</span> minutes <span class="token operator">:</span> minutes<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">":"</span><span class="token punctuation">;</span>
			timeString <span class="token operator">+=</span> $scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>showSeconds <span class="token operator">?</span> <span class="token punctuation">(</span>seconds <span class="token operator">&lt;</span> <span class="token number">10</span> <span class="token operator">?</span> <span class="token string">"0"</span> <span class="token operator">+</span> seconds <span class="token operator">:</span> seconds<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token string">'00'</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span>
		<span class="token keyword">return</span> timeString<span class="token punctuation">;</span>
	<span class="token punctuation">}</span><span class="token punctuation">;</span>
	
	c<span class="token punctuation">.</span><span class="token function-variable function">storeValue</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
		c<span class="token punctuation">.</span>storedValue <span class="token operator">=</span> c<span class="token punctuation">.</span><span class="token function">stringFromDate</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>selectedTime<span class="token punctuation">)</span><span class="token punctuation">;</span>
		$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">setValue</span><span class="token punctuation">(</span>$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>field<span class="token punctuation">.</span>name<span class="token punctuation">,</span> c<span class="token punctuation">.</span>storedValue<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span><span class="token punctuation">;</span>

	<span class="token keyword">var</span> fieldChangeListener <span class="token operator">=</span> $rootScope<span class="token punctuation">.</span><span class="token function">$on</span><span class="token punctuation">(</span><span class="token string">"field.change."</span><span class="token operator">+</span>$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>field<span class="token punctuation">.</span>name<span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">evt<span class="token punctuation">,</span> parms</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
		<span class="token keyword">if</span><span class="token punctuation">(</span>parms<span class="token punctuation">.</span>newValue <span class="token operator">!=</span> c<span class="token punctuation">.</span>storedValue<span class="token punctuation">)</span><span class="token punctuation">{</span>
			c<span class="token punctuation">.</span>selectedTime <span class="token operator">=</span> c<span class="token punctuation">.</span><span class="token function">dateFromString</span><span class="token punctuation">(</span>parms<span class="token punctuation">.</span>newValue<span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span>
	<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	$scope<span class="token punctuation">.</span><span class="token function">$on</span><span class="token punctuation">(</span><span class="token string">"$destroy"</span><span class="token punctuation">,</span> fieldChangeListener<span class="token punctuation">)</span><span class="token punctuation">;</span>
	
	<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span><span class="token string">'showSpinners'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> $scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>showSpinners <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span><span class="token string">'showMeridian'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> $scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>showMeridian <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span><span class="token string">'showSeconds'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> $scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>showSeconds <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span><span class="token string">'hourStep'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> $scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>hourStep <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span><span class="token string">'minuteStep'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> $scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>minuteStep <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span><span class="token string">'secondStep'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> $scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>secondStep <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span><span class="token string">'readonlyInput'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> $scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>readonlyInput <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span><span class="token string">'mousewheel'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> $scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>mousewheel <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span><span class="token string">'arrowkeys'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> $scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>arrowkeys <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span><span class="token function">hasOwnProperty</span><span class="token punctuation">(</span><span class="token string">'meridians'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> $scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>meridians <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'AM'</span><span class="token punctuation">,</span> <span class="token string">'PM'</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
	$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>max <span class="token operator">=</span> c<span class="token punctuation">.</span><span class="token function">dateFromString</span><span class="token punctuation">(</span>$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>max<span class="token punctuation">)</span><span class="token punctuation">;</span>
	$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>min <span class="token operator">=</span> c<span class="token punctuation">.</span><span class="token function">dateFromString</span><span class="token punctuation">(</span>$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>min<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token keyword">var</span> currentValue <span class="token operator">=</span> $scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span>$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>field<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>
	c<span class="token punctuation">.</span>selectedTime <span class="token operator">=</span> currentValue<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'{'</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">?</span> c<span class="token punctuation">.</span><span class="token function">dateFromString</span><span class="token punctuation">(</span>$scope<span class="token punctuation">.</span>options<span class="token punctuation">.</span>defaultTime<span class="token punctuation">)</span> <span class="token operator">:</span> c<span class="token punctuation">.</span><span class="token function">dateFromString</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">)</span><span class="token punctuation">;</span>
	c<span class="token punctuation">.</span><span class="token function">storeValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
        </div>
    </div>
</div>



<!--kg-card-end: html-->
<p>This example widget shows how to create a reusable option for capturing a type of input not normally supported: a "time" input. Dictionary entries have the type Time available, but variables only have Date and Date/Time. So, if you have a record producer for a record with a time field (like the sys_holiday table), you normally would have to use a Single Line Text variable and either add validation on the form to prevent invalid time values like "5 PM" or use a record producer script to transform the user input into a value that the Time field on the produced record will accept. This Time Picker widget both improves the user experience and ensures data is always in the correct format. This widget displays as an upgraded input pre-submit, supports defining options for each instance, and is integrated with the form so that it can be treated just like any other variable.</p><h3 id="1-how-to-display-post-submit"><u>1. How to display post-submit</u></h3><p>If your widget captures user data, you'll likely want to display that data in the requested item after the form is submitted. (If you're using a record producer, you can skip this section if the variable editor for the submitted information isn't shown on the produced record.) Prior to submitting the form, this post assumes that the catalog items including the custom widgets will only be accessed in the Service Portal where the widget will always display for the variable. However, the submitted requested item can be accessed in all three different UIs: portals, classic environments (native UI), and workspaces. Because the widgets use AngularJS, they won't render in the classic environment or a workspace. There are three main options for displaying the data post-submit:</p><ol><li>If the data captured by your widget can display as Single Line Text, then the variable with your embedded widget can use that type instead of Custom or Custom with Label.<ol><li>This will result in Service Portal showing your widget still, while elsewhere it will show as a Single Line Text.</li><li>This option is used by this example widget.</li><li><em>Note: the Widget field doesn't show on the variable form for the type Single Line Text; either add the Widget value from the list view of variables or set the type to Custom, update the Widget field, and then switch back to Single Line Text.</em></li></ol></li><li>If the submitted data for the widget needs to display as a different variable type (like a reference or a date), create or use a separate variable for storing the data and displaying post-submit.<ol><li>Set this variable's value on change of your input or on submit and set the widget variable to only show pre-submit in the catalog item and the other variable to only show post-submit in requested items and catalog tasks.</li><li><em>In example #3, we use this approach and also handle the hiding and showing in the widget's client script itself.</em></li></ol></li><li>If you don't want the submitted data to just display as a normal field and need a custom element to show even in the classic environment and workspaces, your only option is to create a custom UI macro and a custom Next Experience component and include them in the Macro and Macroponent fields in your variable.<ol><li>This option requires significant work because you have to create entirely new versions of your widget using two totally different frameworks (unless you wanted to go with a complicated solution where your macro and component use an iframe to embed a portal page with just your widget and update your widget to take values from URL parameters—but I wouldn't recommend that).</li><li>Also, when using a macro in the classic environment it might not allow you to set the value of that variable using g_form.</li></ol></li></ol><h3 id="2-how-to-define-options-for-each-instance-of-the-widget"><u>2. How to define options for each instance of the widget</u></h3><p>For this widget, we use&nbsp;<a href="https://angular-ui.github.io/bootstrap/versioned-docs/1.1.2/?ref=relay.semaphorepartners.com">UI Bootstrap</a>'s uib-timepicker directive which is included by default in the Service Portal. That directive includes many different options you can specify, like whether to include seconds and whether to use 24- or 12-hour time. In this example, passing options to a specific instance of the widget is not required but is supported (e.g. if you wanted one Work Start variable with a "max" value of 12:59:59 and another Work End variable with a "min" value of 13:00:00).</p><p>To specify options for an embedded widget, include an object in the "Default value" field of the variable. For example:</p><pre><code class="language-javascript">{
   "mousewheel": false,
   "showSeconds": true,
   "defaultTime": "09:00:00",
   "max": "12:59:59"
}
</code></pre><p>The embedded widget instance can then access the options data in a few different places:</p><ol><li>From the $scope data. Can use either $scope.options, $scope.widget_parameters, or $scope.widget.options (I don't think there's any difference between these three).</li><li>From the "options" object in the server script, which can be made available to the client script with a line like:&nbsp;<code>data.options = options;</code>&nbsp;(this option is mainly useful if you have on load server code that depends on and potentially updates the options).</li><li>From the g_form value of its variable using&nbsp;<code>$scope.page.g_form.getValue($scope.page.field.name);</code>.</li></ol><p>If the widget won't display in the portal post-submit and the form doesn't support drafts, the approach used won't matter because the widget will only pull options once on initial catalog item load. Otherwise, you'll need to consider the following:</p><ol><li>Approach #3 shouldn't be used if the widget overwrites its own g_form value to store the user's input.</li><li>Approach #1 and #2 will use the current default value from the variable, whereas approach #3 will keep whatever the value was when the form was submitted or the draft was saved. So, approach #3 is great if the options can change and more accurately represent the "current state" of the widget. However, with approach #3 you'll need to be careful about changing your option schema because the widget will still need to support options from previously submitted/saved requests.</li></ol><p><em>Note: if using approach #1 or #2, the options object will also include some properties added by default. So, make sure to not accidentally use the same property name and be aware they are present if you plan to iterate through the options. The options currently included automatically are: preserve_placeholder_size, advanced_placeholder_dimensions, cat_item, sp_widget_dv, sp_column_dv, active, sys_tags, order, and async_load.</em></p><h3 id="3-how-to-make-your-widget-act-like-a-normal-variable"><u>3. How to make your widget act like a normal variable</u></h3><p>Our goal with a widget like this (which uses type Single Line Text and stores the input in its own variable) is that, other than setting the Widget field and potentially the Default value for the variable, the variable shouldn't require any special handling and should just work the same as any normal variable. With the approach this example uses, the widget only needs a little extra logic for this:</p><ol><li>For client scripts, UI policies, etc. to be able to get and set the value of your custom input, you'll need to sync the value of your custom input with the variable's g_form value.<ol><li>The simplest way is to include the following in your input:&nbsp;<code>ng-model="page.fieldValue" ng-model-options="{updateOn: 'blur', getterSetter: true}"</code>.</li><li>However, in this example, the value needs to be transformed both when getting and setting because the uib-timepicker uses a Date object whereas the variable's value should have a string formatted as "HH:mm:ss". So, for setting the variable's value you can add an ng-blur or ng-change function to your input and have that function transform the value and then store it using&nbsp;<code>$scope.page.g_form.setValue($scope.page.field.name, value);</code>. For updating your custom input when something else changes the variable's value, you can watch for the field.change event on $rootScope (as described in point #2 of the first example) and transform the data before updating the variable you're using for your ng-model.</li></ol></li><li>For handling read-only, you'll have to add your own logic to prevent the user from interacting with your custom elements while the widget's variable is read-only. For this example, this was as easy as adding this attribute to the input element:&nbsp;<code>ng-disabled="page.field.isReadonly()"</code>.</li></ol><h2 id="example-3-country-typeahead-picker">Example 3: Country Typeahead Picker</h2>
<!--kg-card-begin: html-->
<div class="code-tabs">
    <div class="tab-buttons">
        <button class="tab-btn active"  onclick="switchTab(this,'tabs-2-0')">Screenshot</button>
        <button class="tab-btn"         onclick="switchTab(this,'tabs-2-1')">HTML</button>
        <button class="tab-btn"         onclick="switchTab(this,'tabs-2-2')">Client Script</button>
<button class="tab-btn"         onclick="switchTab(this,'tabs-2-3')">CSS</button>
<button class="tab-btn"         onclick="switchTab(this,'tabs-2-4')">Link Function</button>
    </div>
    <div class="tab-panels">
        <div id="tabs-2-0" class="tab-panel tab-panel-light active">
            <img src="https://www.supernow-blog.com/images/country-typeahead-picker-widget.png" />
        </div>
        <div id="tabs-2-1" class="tab-panel tab-panel-code">
           <pre class="language-html" tabindex="0">
<code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">ng-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c.isVisible<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text/ng-template<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>typeaheadAddress.html<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">ng-src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>{{match.model.flags.png}}<span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>16<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">ng-bind-html</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>match.label | uibTypeaheadHighlight:query<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>form-group country-typeahead<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">ng-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c.isMandatory<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>fa fa-asterisk mandatory<span class="token punctuation">"</span></span> <span class="token attr-name">ng-class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>{'mandatory-filled': c.selectedCountry}<span class="token punctuation">"</span></span> <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>{{c.selectedCountry ? 'Required Filled' : 'Required'}}<span class="token punctuation">"</span></span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>img<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>typeahead-{{page.field.name}}<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>{{c.label}}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">ng-show</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c.loadingLocations<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>loader<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>typeahead-{{page.field.name}}<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token attr-name">ng-model</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c.selectedCountry<span class="token punctuation">"</span></span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Search for country<span class="token punctuation">"</span></span> <span class="token attr-name">ng-disabled</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c.isReadOnly<span class="token punctuation">"</span></span>
    <span class="token attr-name">typeahead-on-select</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c.select($item)<span class="token punctuation">"</span></span> <span class="token attr-name">uib-typeahead</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>address as address.name.common for address in c.getLocation($viewValue)<span class="token punctuation">"</span></span>
    <span class="token attr-name">typeahead-select-on-blur</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>false<span class="token punctuation">"</span></span> <span class="token attr-name">ng-blur</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c.blur()<span class="token punctuation">"</span></span> <span class="token attr-name">typeahead-loading</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>c.loadingLocations<span class="token punctuation">"</span></span> <span class="token attr-name">typeahead-template-url</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>typeaheadAddress.html<span class="token punctuation">"</span></span> <span class="token attr-name">typeahead-show-hint</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span> 
    <span class="token attr-name">typeahead-no-results</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>noResults<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>form-control<span class="token punctuation">"</span></span> <span class="token attr-name">typeahead-wait-ms</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>300<span class="token punctuation">"</span></span> <span class="token attr-name">typeahead-min-length</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>0<span class="token punctuation">"</span></span> <span class="token attr-name">typeahead-popup-limit</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>10<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code>
</pre>
           
        </div>
        <div id="tabs-2-2" class="tab-panel tab-panel-code">
<pre class="language-javascript" tabindex="0">
<code class="language-javascript">api<span class="token punctuation">.</span><span class="token function-variable function">controller</span><span class="token operator">=</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">$scope<span class="token punctuation">,</span> $http<span class="token punctuation">,</span> $timeout</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
	<span class="token keyword">var</span> c <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">;</span>
	<span class="token keyword">var</span> currentValue <span class="token operator">=</span> $scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span>$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>field<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">)</span> currentValue <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span><span class="token punctuation">(</span>currentValue <span class="token operator">&amp;&amp;</span> currentValue<span class="token punctuation">.</span>linkedField<span class="token punctuation">)</span><span class="token punctuation">{</span>
		c<span class="token punctuation">.</span><span class="token function-variable function">select</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
			$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">setValue</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">.</span>linkedField<span class="token punctuation">,</span> item<span class="token punctuation">.</span>name<span class="token punctuation">.</span>common<span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span><span class="token punctuation">;</span>

		c<span class="token punctuation">.</span><span class="token function-variable function">blur</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
			<span class="token comment">//timeout to give time for c.select to run first if an option was clicked</span>
			<span class="token function">$timeout</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
				<span class="token comment">//if an option was selected then c.selectedCountry would change from string to object</span>
				<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token keyword">typeof</span> c<span class="token punctuation">.</span>selectedCountry <span class="token operator">===</span> <span class="token string">'string'</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
					<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>c<span class="token punctuation">.</span>loadingLocations <span class="token operator">&amp;&amp;</span> c<span class="token punctuation">.</span>lastChecked <span class="token operator">==</span> c<span class="token punctuation">.</span>selectedCountry<span class="token punctuation">)</span><span class="token punctuation">{</span>
						<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>c<span class="token punctuation">.</span>lastResults <span class="token operator">||</span> <span class="token operator">!</span>c<span class="token punctuation">.</span>lastResults<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">{</span>
							c<span class="token punctuation">.</span>selectedCountry <span class="token operator">=</span> <span class="token string">''</span>
							<span class="token keyword">if</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">)</span> $scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">clearValue</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">.</span>linkedField<span class="token punctuation">)</span><span class="token punctuation">;</span>
						<span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>lastResults<span class="token punctuation">.</span>length <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
							c<span class="token punctuation">.</span>selectedCountry <span class="token operator">=</span> c<span class="token punctuation">.</span>lastResults<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
							<span class="token keyword">if</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">)</span> $scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">setValue</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">.</span>linkedField<span class="token punctuation">,</span> c<span class="token punctuation">.</span>lastResults<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>name<span class="token punctuation">.</span>common<span class="token punctuation">)</span><span class="token punctuation">;</span>
						<span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>lastResults<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
							c<span class="token punctuation">.</span>selectedCountry <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>
							<span class="token keyword">if</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">)</span> $scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">clearValue</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">.</span>linkedField<span class="token punctuation">)</span><span class="token punctuation">;</span>
						<span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>
							c<span class="token punctuation">.</span>deferBlur <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
						<span class="token punctuation">}</span>
					<span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>
						c<span class="token punctuation">.</span>deferBlur <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
					<span class="token punctuation">}</span>
				<span class="token punctuation">}</span>
			<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span><span class="token punctuation">;</span>

		c<span class="token punctuation">.</span><span class="token function-variable function">getLocation</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">val</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
			<span class="token keyword">var</span> apiToUse <span class="token operator">=</span> val <span class="token operator">?</span> <span class="token string">'https://restcountries.com/v3.1/name/'</span><span class="token operator">+</span> val <span class="token operator">:</span> <span class="token string">'https://restcountries.com/v3.1/all'</span>
			<span class="token keyword">return</span> $http<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>apiToUse<span class="token punctuation">,</span> <span class="token punctuation">{</span>
				<span class="token literal-property property">params</span><span class="token operator">:</span> <span class="token punctuation">{</span>
					<span class="token literal-property property">fields</span><span class="token operator">:</span> <span class="token string">'cca2,cca3,name,translations,nativeName,altSpellings,flags,independent'</span>
				<span class="token punctuation">}</span>
			<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
				c<span class="token punctuation">.</span>lastChecked <span class="token operator">=</span> val<span class="token punctuation">;</span>
				<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>currentValue<span class="token punctuation">.</span>includeTerritories<span class="token punctuation">)</span><span class="token punctuation">{</span>
					response<span class="token punctuation">.</span>data <span class="token operator">=</span> response<span class="token punctuation">.</span>data<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">country</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
						<span class="token keyword">return</span> country<span class="token punctuation">.</span>independent <span class="token operator">===</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
					<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
				<span class="token punctuation">}</span>
				c<span class="token punctuation">.</span>lastResults <span class="token operator">=</span> response<span class="token punctuation">.</span>data<span class="token punctuation">;</span>
				$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">hideFieldMsg</span><span class="token punctuation">(</span>$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>field<span class="token punctuation">.</span>name<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
				<span class="token keyword">if</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>deferBlur<span class="token punctuation">)</span><span class="token punctuation">{</span>
					c<span class="token punctuation">.</span>deferBlur <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
					c<span class="token punctuation">.</span>loadingLocations <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
					c<span class="token punctuation">.</span><span class="token function">blur</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
				<span class="token punctuation">}</span>
				<span class="token keyword">return</span> response<span class="token punctuation">.</span>data<span class="token punctuation">;</span>
			<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
				c<span class="token punctuation">.</span>lastChecked <span class="token operator">=</span> val<span class="token punctuation">;</span>
				c<span class="token punctuation">.</span>lastResults <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>
				$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">showFieldMsg</span><span class="token punctuation">(</span>$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>field<span class="token punctuation">.</span>name<span class="token punctuation">,</span> <span class="token string">'Could not find a country matching "'</span><span class="token operator">+</span>val<span class="token operator">+</span><span class="token string">'"'</span><span class="token punctuation">,</span> <span class="token string">'error'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
				<span class="token keyword">if</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>deferBlur<span class="token punctuation">)</span><span class="token punctuation">{</span>
					c<span class="token punctuation">.</span>deferBlur <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
					c<span class="token punctuation">.</span>loadingLocations <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
					c<span class="token punctuation">.</span><span class="token function">blur</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
				<span class="token punctuation">}</span>
				<span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
			<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span><span class="token punctuation">;</span>

		c<span class="token punctuation">.</span><span class="token function-variable function">init</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
			c<span class="token punctuation">.</span>selectedCountry <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>
			c<span class="token punctuation">.</span>lastResults <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>
			c<span class="token punctuation">.</span>lastChecked <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>
			c<span class="token punctuation">.</span>deferBlur <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
			c<span class="token punctuation">.</span>isMandatory <span class="token operator">=</span> $scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">isMandatory</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">.</span>linkedField<span class="token punctuation">)</span><span class="token punctuation">;</span>
			c<span class="token punctuation">.</span>isVisible <span class="token operator">=</span> $scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">isVisible</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">.</span>linkedField<span class="token punctuation">)</span><span class="token punctuation">;</span>
			c<span class="token punctuation">.</span>isReadOnly <span class="token operator">=</span> $scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">isReadOnly</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">.</span>linkedField<span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token keyword">var</span> linkedFieldDetails <span class="token operator">=</span> $scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">getField</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">.</span>linkedField<span class="token punctuation">)</span><span class="token punctuation">;</span>
			c<span class="token punctuation">.</span>label <span class="token operator">=</span> linkedFieldDetails <span class="token operator">?</span> linkedFieldDetails<span class="token punctuation">.</span>label <span class="token operator">:</span> <span class="token string">'Country'</span><span class="token punctuation">;</span>
			$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span>$<span class="token keyword">private</span><span class="token punctuation">.</span>events<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">"onPropertyChange"</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">type<span class="token punctuation">,</span> field<span class="token punctuation">,</span> attribute<span class="token punctuation">,</span> value</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
				<span class="token keyword">if</span><span class="token punctuation">(</span>type <span class="token operator">==</span> <span class="token string">'FIELD'</span> <span class="token operator">&amp;&amp;</span> field <span class="token operator">==</span> currentValue<span class="token punctuation">.</span>linkedField<span class="token punctuation">)</span><span class="token punctuation">{</span>
					<span class="token keyword">if</span><span class="token punctuation">(</span>attribute <span class="token operator">==</span> <span class="token string">'visible'</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
						c<span class="token punctuation">.</span>isVisible <span class="token operator">=</span> value<span class="token punctuation">;</span>
					<span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>attribute <span class="token operator">==</span> <span class="token string">'readonly'</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
						c<span class="token punctuation">.</span>isReadOnly <span class="token operator">=</span> value<span class="token punctuation">;</span>
					<span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>attribute <span class="token operator">==</span> <span class="token string">'mandatory'</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
						c<span class="token punctuation">.</span>isMandatory <span class="token operator">=</span> value<span class="token punctuation">;</span>
					<span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>attribute <span class="token operator">==</span> <span class="token string">'messages'</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
						$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">hideFieldMsg</span><span class="token punctuation">(</span>$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>field<span class="token punctuation">.</span>name<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
						<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> value<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
							$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">showFieldMsg</span><span class="token punctuation">(</span>$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>field<span class="token punctuation">.</span>name<span class="token punctuation">,</span> value<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>message<span class="token punctuation">,</span> value<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>type<span class="token punctuation">)</span><span class="token punctuation">;</span>
						<span class="token punctuation">}</span>
					<span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>attribute <span class="token operator">==</span> <span class="token string">'label'</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
						c<span class="token punctuation">.</span>label <span class="token operator">=</span> value<span class="token punctuation">;</span>
					<span class="token punctuation">}</span>
				<span class="token punctuation">}</span>
			<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token keyword">var</span> linkedFieldValue <span class="token operator">=</span> $scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span>currentValue<span class="token punctuation">.</span>linkedField<span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token keyword">if</span><span class="token punctuation">(</span>linkedFieldValue<span class="token punctuation">)</span><span class="token punctuation">{</span>
				c<span class="token punctuation">.</span>selectedCountry <span class="token operator">=</span> linkedFieldValue<span class="token punctuation">;</span>
				c<span class="token punctuation">.</span>deferBlur <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
				c<span class="token punctuation">.</span><span class="token function">getLocation</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token punctuation">}</span>
			$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span>$<span class="token keyword">private</span><span class="token punctuation">.</span>events<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'onChange'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">field<span class="token punctuation">,</span> blank<span class="token punctuation">,</span> value</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
				<span class="token keyword">if</span><span class="token punctuation">(</span>field <span class="token operator">==</span> currentValue<span class="token punctuation">.</span>linkedField<span class="token punctuation">)</span><span class="token punctuation">{</span>
					<span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>c<span class="token punctuation">.</span>selectedCountry <span class="token operator">||</span> <span class="token keyword">typeof</span> c<span class="token punctuation">.</span>selectedCountry <span class="token operator">===</span> <span class="token string">'string'</span> <span class="token operator">||</span> c<span class="token punctuation">.</span>selectedCountry<span class="token punctuation">.</span>name<span class="token punctuation">.</span>common <span class="token operator">!=</span> value<span class="token punctuation">)</span><span class="token punctuation">{</span>
						c<span class="token punctuation">.</span>selectedCountry <span class="token operator">=</span> value<span class="token punctuation">;</span>
						<span class="token keyword">if</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>selectedCountry<span class="token punctuation">)</span><span class="token punctuation">{</span>
							c<span class="token punctuation">.</span>deferBlur <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
							c<span class="token punctuation">.</span><span class="token function">getLocation</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>
						<span class="token punctuation">}</span>
					<span class="token punctuation">}</span>
				<span class="token punctuation">}</span>
			<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
			$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span>$<span class="token keyword">private</span><span class="token punctuation">.</span>events<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'submit'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
				$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">clearValue</span><span class="token punctuation">(</span>$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>field<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token keyword">var</span> el <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">'.sp-form-container #'</span><span class="token operator">+</span>currentValue<span class="token punctuation">.</span>linkedField<span class="token punctuation">)</span><span class="token punctuation">;</span>
			<span class="token keyword">if</span><span class="token punctuation">(</span>el<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> el<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>style<span class="token punctuation">)</span> el<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>style<span class="token punctuation">.</span>display <span class="token operator">=</span> <span class="token string">'none'</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span><span class="token punctuation">;</span>
		c<span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>
		$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>g_form<span class="token punctuation">.</span><span class="token function">setDisplay</span><span class="token punctuation">(</span>$scope<span class="token punctuation">.</span>page<span class="token punctuation">.</span>field<span class="token punctuation">.</span>name<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code>
</pre>
        </div>
 <div id="tabs-2-3" class="tab-panel tab-panel-code">
<pre class="language-css" tabindex="0">
<code class="language-css"><span class="token comment">/* loading icon */</span>
<span class="token selector">.loader</span> <span class="token punctuation">{</span>
  <span class="token property">border</span><span class="token punctuation">:</span> 2px solid #b7b7b7<span class="token punctuation">;</span>
  <span class="token property">border-top</span><span class="token punctuation">:</span> 2px solid #3498db<span class="token punctuation">;</span>
  <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 14px<span class="token punctuation">;</span>
  <span class="token property">height</span><span class="token punctuation">:</span> 14px<span class="token punctuation">;</span>
  <span class="token property">animation</span><span class="token punctuation">:</span> spin 2s linear infinite<span class="token punctuation">;</span>
  <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token atrule"><span class="token rule">@keyframes</span> spin</span> <span class="token punctuation">{</span>
	<span class="token selector">0%</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">rotate</span><span class="token punctuation">(</span>0deg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>	
	<span class="token selector">100%</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">rotate</span><span class="token punctuation">(</span>360deg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">/* to limit the height of the dropdown and to include a scrollbar */</span>
<span class="token selector">.dropdown-menu</span> <span class="token punctuation">{</span>
  <span class="token property">max-height</span><span class="token punctuation">:</span> 250px<span class="token punctuation">;</span>
  <span class="token property">overflow-y</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.country-typeahead .dropdown-menu::-webkit-scrollbar</span> <span class="token punctuation">{</span>
  	 <span class="token property">-webkit-appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
    <span class="token property">width</span><span class="token punctuation">:</span> 8px<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.country-typeahead .dropdown-menu::-webkit-scrollbar-thumb</span> <span class="token punctuation">{</span>
    <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span>
    <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.4<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.country-typeahead .dropdown-menu::-webkit-scrollbar-track</span> <span class="token punctuation">{</span>
    <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.1<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code>
</pre>
        </div>
<div id="tabs-2-4" class="tab-panel tab-panel-code">
<pre class="language-javascript" tabindex="0">
<code class="language-javascript"><span class="token keyword">function</span> <span class="token function">link</span><span class="token punctuation">(</span><span class="token parameter">scope<span class="token punctuation">,</span> element<span class="token punctuation">,</span> attrs<span class="token punctuation">,</span> controller</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    element<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'mousedown'</span><span class="token punctuation">,</span> <span class="token string">'.dropdown-menu'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>target <span class="token operator">===</span> e<span class="token punctuation">.</span>currentTarget <span class="token operator">||</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span><span class="token string">'dropdown-menu'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
            e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code>
</pre>
        </div>
    </div>
</div>
<!--kg-card-end: html-->
<p>In this example, we are replacing an existing Single Line Text variable called "Country" with a custom input that uses&nbsp;<a href="https://angular-ui.github.io/bootstrap/versioned-docs/1.1.2/?ref=relay.semaphorepartners.com">UI Bootstrap</a>'s uib-typeahead directive and an API call to&nbsp;<a href="https://restcountries.com/?ref=relay.semaphorepartners.com">restcountries.com</a>&nbsp;to create a typeahead dropdown of country values. Rather than storing the user's input in its own variable like in the previous example, this example stores the data in the existing country variable and mirrors the state of that variable (so that any existing logic referencing the existing variable will automatically apply to this new custom input without needing to be updated).</p><p>Storing the user input in the widget's own variable is often simpler, but that approach doesn't work as well if:</p><ol><li>The captured input needs to be stored as something other than Single Line Text.</li><li>The input needs to be stored across multiple variables.</li><li>You don't want to modify the existing variable you'll be replacing (e.g. the variable is within a variable set used by a lot of other forms).</li><li>You conditionally need to show either the existing variable or the upgraded custom variable.<ol><li>For example, say you create a similar typeahead field to capture "state" data, but you only have state data for USA. So, when the country is not USA, you want to show an existing Single Line Text variable instead of your new typeahead state picker. By having the custom state picker variable store its value in the existing variable, other logic can use that existing variable's value as the single source of truth for the state value.</li></ol></li></ol><p><em>This example widget could have been created as a "self-contained" widget like the previous example, but for demonstration purposes we'll show what it would look like if we wanted to create a separate variable for the widget rather than updating the existing Country field to include a widget.</em></p><h3 id="1-how-to-determining-preor-post-submit"><u>1. How to Determining Pre- or Post-Submit</u></h3><p>In this example, post-submit we want to hide the widget's variable and instead show the existing country variable that we copy the data to. We use the variable type Custom because that will keep the the variable from showing in the classic environment or workspaces:</p>
<!--kg-card-begin: html-->
<ol>
<li>In the Classic Environment, a variable with type Custom will not display at all if the variable record has a value for the Widget field and doesn't have a value for the Macro field.</li>
<li>In workspaces, the widget won't display (assuming the variable is type Custom and doesn't have a Macroponent value); however, the variable will still take up a small amount of space in the form's variable editor. 
<ol>
<li><em>If this extra space is unacceptable, you can manually hide the widget variable post-submit. If you want a more reusable option to handle hiding multiple widgets like this, you can add an onLoad client script with UI Type "Mobile / Service Portal" and a script like: <code>g_form.getFieldNames().filter(f =&gt; f.startsWith('variables.') &amp;&amp; g_form.getControl(f).type == 'macro').forEach(f =&gt; g_form.setDisplay(f, false));</code>. You can either create that client script in the catalog item/variable set (and check the boxes for "Applies on Requested Items" and "Applies on Catalog Tasks") or directly in the Requested Item and Catalog Task tables themselves.</em></li>
</ol></li>
<li>In the portal, a variable of type Custom with a Widget value will normally display and the widget code will run. So, we have to add handling to our widget to limit its logic to only run pre-submit and to hide the widget's variable post-submit. To identify in the widget's client script whether it is running pre- or post-submit, there are two main options:
<ol>
<li>OnSubmit of the form, set the value of the widget variable (or some other variable) to be a value that it would never have prior to the form being submitted. This approach is used for this widget. We have logic like this that clears the widget's value onSubmit:
<pre><code class="hljs language-javascript">$scope.<span class="hljs-property">page</span>.<span class="hljs-property">g_form</span>.<span class="hljs-property">$private</span>.<span class="hljs-property">events</span>.<span class="hljs-title function_">on</span>(<span class="hljs-string">'submit'</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>){
    $scope.<span class="hljs-property">page</span>.<span class="hljs-property">g_form</span>.<span class="hljs-title function_">clearValue</span>($scope.<span class="hljs-property">page</span>.<span class="hljs-property">field</span>.<span class="hljs-property">name</span>);
});</code></pre>
<p>And we wrap the client code in an if-condition like this so that it only runs when the variable has a value:</p>
<pre><code class="hljs language-javascript"><span class="hljs-keyword">var</span> currentValue = $scope.<span class="hljs-property">page</span>.<span class="hljs-property">g_form</span>.<span class="hljs-title function_">getValue</span>($scope.<span class="hljs-property">page</span>.<span class="hljs-property">field</span>.<span class="hljs-property">name</span>);
<span class="hljs-keyword">if</span>(currentValue) currentValue = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(currentValue);
<span class="hljs-keyword">if</span>(currentValue &amp;&amp; currentValue.<span class="hljs-property">linkedField</span>){</code></pre>
<p><em>I prefer clearing the value rather than setting it to a specific value like "submitted", so that the variable won't show a value anywhere, such as the variable summary that shows for approvals.</em></p></li>
<li>The other approach relies on the fact that <code>$scope.page.g_form.getUniqueValue();</code> will always return the sys_id of the catalog item, but post-submit other places will switch to having the sys_id of the submitted request. 
<ol>
<li>Can compare to the g_form object attached to the <code>sp-variable-layout</code> directive, which contains the form both pre- and post-submit: 
<pre><code class="hljs language-javascript">c.<span class="hljs-property">isPostSubmit</span> = ($scope.<span class="hljs-property">page</span>.<span class="hljs-property">g_form</span>.<span class="hljs-title function_">getUniqueValue</span>() == angular.<span class="hljs-title function_">element</span>($(<span class="hljs-string">"sp-variable-layout"</span>)).<span class="hljs-title function_">scope</span>().<span class="hljs-title function_">getGlideForm</span>().<span class="hljs-title function_">getUniqueValue</span>()));</code></pre></li>
<li>Can compare to the g_form object broadcast in the event <code>spModel.gForm.rendered</code>:
<pre><code class="hljs language-javascript"><span class="hljs-keyword">var</span> g_formListener = $rootScope.$on(<span class="hljs-string">'spModel.gForm.rendered'</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params">e, gFormInstance</span>) {
	c.<span class="hljs-property">isPostSubmit</span> = (gFormInstance.<span class="hljs-title function_">getSysId</span>() == $scope.<span class="hljs-property">page</span>.<span class="hljs-property">g_form</span>.<span class="hljs-title function_">getUniqueValue</span>());
});
$scope.$on(<span class="hljs-string">"$destroy"</span>, g_formListener);</code></pre></li>
<li>Can compare to the sys_id in the page's URL (making sure to inject $location into your controller): 
<pre><code class="hljs language-javascript">c.<span class="hljs-property">isPostSubmit</span> = ($location.<span class="hljs-title function_">search</span>().<span class="hljs-property">sys_id</span> == $scope.<span class="hljs-property">page</span>.<span class="hljs-property">g_form</span>.<span class="hljs-title function_">getUniqueValue</span>());</code></pre></li>
</ol></li>
</ol></li>
</ol>
<!--kg-card-end: html-->
<p><em>We don't use Custom with Label because we want this custom input's label to show as mandatory, which isn't supported for the two Custom variable types. Instead, our widget's HTML will include custom label and mandatory asterisk elements that match the styling of normal variables' labels.</em></p><h3 id="2-how-to-mirror-an-existing-variable"><u>2. How to mirror an existing variable</u></h3><p>We want this widget set up so that any existing logic that applies to the existing variable (like a UI Policy that makes it read-only or a client script that shows a field message for it) will automatically apply to the new custom variable as well. The goal is that adding this custom country typeahead picker to a form should be as easy as just creating the new Custom type variable referencing this widget and specifying the "linkedField" in the default value.</p><p>The steps for achieving this and mirroring an existing variable are:</p><ol><li>Manually hide the existing variable by setting its element to&nbsp;<code>display: 'none'</code>&nbsp;(which is independent of its status of g_form.isMandatory).<ol><li><em>As mentioned earlier, post-submit we'll instead hide the widget's variable and let the existing variable work as normal—no mirroring needed.</em></li><li><em>In some cases, you might want the existing variable to conditionally show instead of the widget's variable. If that's the case, you won't want to mirror the visible status of the existing variable; instead, you can use normal UI policies or client scripts to handle which field to show. If you need to prevent some client script logic from running while the widget's variable is hidden you can wrap it in an if-statement like:&nbsp;<code>if($scope.page.field.isVisible()){</code>.</em></li></ol></li><li>On load, get the existing variable's current value and its mandatory, visible, and read-only status using g_form and store those values in the widget's scope for the HTML to use for manually setting its custom input to hidden, not mandatory, or disabled.</li><li>Additionally, set up listeners for syncing the widget's custom input with any changes made to the existing variable or its value.<ol><li>In this example, we use the private g_form event "onChange" to listen for changes to the existing variable's value and the event "onPropertyChange" to listen for the following changes related to the variable: visible, readonly, mandatory, messages (field messages), and label.<ol><li><em>If you prefer not to use the private g_form events, you could instead listen to changes to the existing variable's value using $rootScope, and for the other attributes of the existing variable you could store a reference to its field object retrieved with g_form.getField and use the functions isMandatory, isReadonly, and isVisible included there.</em></li></ol></li><li>We don't have to worry about syncing anything back to the existing variable other than the value because we're expecting all client scripts and UI policies to reference the existing variable rather than the widget variable itself.</li></ol></li></ol> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ You Don&#x27;t Need a Spoke for That: Building a Box Integration Without Integration Hub ]]></title>
        <description><![CDATA[ How we built a fully-functional Box integration using ServiceNow&#39;s native REST capabilities – no additional licensing required


The Great Integration Hub Paradox

ServiceNow&#39;s marketing is brilliant in its simplicity. &quot;It just works.&quot; or &quot;We have a spoke for that.&quot; And when they&#39; ]]></description>
        <link>https://relay.semaphorepartners.com/articles/you-dont-need-a-spoke-for-that-building-a-box-integration-without-integration-hub/</link>
        <guid isPermaLink="false">6936ef41f79a0600015a8c3e</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Tue, 16 Dec 2025 10:20:17 -0500</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/12/Copy-of-Add-a-heading-1.png" medium="image"/>
        <content:encoded><![CDATA[ <p><em>How we built a fully-functional Box integration using ServiceNow's native REST capabilities – no additional licensing required</em></p><h2 id="the-great-integration-hub-paradox">The Great Integration Hub Paradox</h2><p>ServiceNow's marketing is brilliant in its simplicity. "It just works." or "We have a spoke for that." And when they're demoing to executives, they love showing a pre-built Flow Designer action as part of a spoke.</p><p>But,<strong> what happens when your customer doesn't pay for that spoke?</strong></p><p>Integration Hub's licensing model is c<em>onfusing</em>. ServiceNow breaks spokes into multiple tiers – Starter, Professional and Enterprise – each requiring separate entitlements. Want to integrate with Box? That's a Professional spoke. Need SAP S/4HANA? Enterprise tier. Oracle HCM? Also Enterprise. The list goes on and on, and we're constanly referencing the latest version of the "pricing-and-packaging-app-engine" document.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/12/image.png" class="kg-image" alt="" loading="lazy" width="738" height="509" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/12/image.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/12/image.png 738w" sizes="(min-width: 720px) 720px"></figure><p><em>Just a sampling of the Professional tier spokes. </em></p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/12/image-2.png" class="kg-image" alt="" loading="lazy" width="737" height="361" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/12/image-2.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/12/image-2.png 737w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/12/image-3.png" class="kg-image" alt="" loading="lazy" width="719" height="792" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/12/image-3.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/12/image-3.png 719w"></figure><p><em>Enterprise spokes add even more cost – and more confusion about what's included.</em></p><p>For organizations that only need one or two integrations, paying for an entire spoke tier feels like buying a whole pizza when you just want a slice. And for ServiceNow partners like us, we need to be able to deliver value regardless of what licensing our clients have.</p><h2 id="the-old-fashioned-way-still-works">The Old Fashioned Way Still Works</h2><p>The good news is that<strong> you can still build integrations the "old fashioned way."</strong> REST Messages, OAuth configurations, and Script Includes aren't going anywhere. They're not deprecated. They're not legacy. They're fully-supported, and – critically – they don't require additional licensing.</p><p>We've done this before with <a href="https://relay.semaphorepartners.com/articles/integrating-servicenow-with-sap-s4-hana-cloud/" rel="noreferrer">SAP</a>, <a href="https://relay.semaphorepartners.com/articles/streamlining-employee-onboarding-integrating-servicenow-with-greenhouse-recruiting/" rel="noreferrer">Greenhouse</a>, <a href="https://relay.semaphorepartners.com/articles/integrating-ukg-with-servicenow-to-supplement-organizational-data/" rel="noreferrer">UKG</a>, and even <a href="https://relay.semaphorepartners.com/articles/when-apis-dont-exist-we-make-our-own-a-fantasy-football-data-integration-story/" rel="noreferrer">ESPN's Fantasy Football</a>. Every time, we read the API documentation, set up the authentication, and build exactly what our clients need. No spokes required.</p><p>When a client came to us needing to integrate with Box for their employee onboarding process, we didn't hesitate. </p><h2 id="building-the-box-integration">Building the Box Integration</h2><h3 id="step-1-creating-an-oauth-application-in-box">Step 1: Creating an OAuth Application in Box</h3><p>The first step was heading to the Box Developer Console and creating a Custom App with OAuth 2.0 authentication. Box's documentation is excellent here – you specify your redirect URI (pointing to your ServiceNow instance), select the scopes you need, and Box generates your client credentials.</p><p>Per the Box documentation, the key scopes we needed were:</p><ul><li><code>root_readwrite</code> - for folder and file operations</li><li><code>sign_requests.readwrite</code> - for Box Sign templates</li></ul><h3 id="step-2-configuring-oauth-in-servicenow">Step 2: Configuring OAuth in ServiceNow</h3><p>ServiceNow's OAuth framework is under-appreciated. We created an OAuth Entity in ServiceNow with the client ID and secret from Box, configured the token and authorization URLs, and ServiceNow handles the rest – including token refreshes.</p><p>This is the magic that spokes obscure: ServiceNow already has robust OAuth2 support built into core platform functionality. The spoke is just a wrapper around capabilities you already have access to.</p><h3 id="step-3-creating-the-rest-message">Step 3: Creating the REST Message</h3><p>We created a single REST Message called "Box" with OAuth2 authentication pointing to our OAuth profile. Then, for each Box API endpoint we need, we added an HTTP Method:</p><ul><li><strong>Get Folder</strong> - <code>GET https://api.box.com/2.0/folders/${folder_id}</code></li><li><strong>Create Folder</strong> - <code>POST https://api.box.com/2.0/folders</code></li><li><strong>Copy Folder</strong> - <code>POST https://api.box.com/2.0/folders/${folder_id}/copy</code></li><li><strong>Get Sign Templates</strong> - <code>GET https://api.box.com/2.0/sign_templates</code></li><li><strong>Create Sign Request</strong> - <code>POST https://api.box.com/2.0/sign_requests</code></li></ul><p>Each method has its parameters defined, and the authentication is inherited from the parent. Want to add a new endpoint later? It's easy, and you can insert &amp; stay on the ones you already have.</p><h3 id="step-4-the-script-include">Step 4: The Script Include</h3><p>The real power comes from wrapping everything in a Script Include. Here's the structure we usually use:</p>

<aside data-content-cta class="relative js-toc-ignore">
        <div data-content-cta-blur class="absolute w-full top-0 -translate-y-full bg-gradient-to-b to-70% from-transparent to-white dark:to-gray-900">
            &nbsp; <br> &nbsp; <br> &nbsp;
        </div>

    <div class="flex flex-col items-center text-center w-full px-4 py-10 lg:px-10 text-base bg-accent-10 rounded-md dark:border dark:border-accent-20">
        <h2 class="text-gray-900 dark:text-gray-50 text-xl sm:text-2xl leading-snug tracking-tight">
                    This post is for subscribers only
        </h2>

            <button data-portal="signup" class="bg-accent text-accent-contrast rounded-lg px-3 py-1.5 mt-7 font-semibold text-base leading-relaxed hover:opacity-85">
                Subscribe now
            </button>
            <p class="mt-5 text-base">
                Already have an account? <button data-portal="signin" class="block sm:inline mx-auto font-medium underline decoration-accent decoration-2 underline-offset-2 hover:text-accent">Sign in</button>
            </p>
    </div>
</aside>
 ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Server-Side UI Actions: Opening Links in a New Tab ]]></title>
        <description><![CDATA[ Links. They go places! But what if we wanted them to go to a different tab?







The Problem


Pick one: current or g_navigation.openPopup





ServiceNow lets us add links to forms via UI Actions, which is great! What&#39;s not so great is that one cannot easily configure ]]></description>
        <link>https://relay.semaphorepartners.com/articles/serveropening-links-in-a-new-tab-via-ui-action/</link>
        <guid isPermaLink="false">68bb5adb7a83bf0001f2c489</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Christopher Crockett ]]></dc:creator>
        <pubDate>Thu, 25 Sep 2025 21:03:56 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/09/Copy-of-Add-a-heading--1-.png" medium="image"/>
        <content:encoded><![CDATA[ <p><em>Links. They go places! But what if we wanted them to go to a different tab?</em></p><div class="kg-card kg-header-card kg-v2 kg-width-regular " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="the-problem" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">The Problem</span></h2>
                    <p id="pick-one-current-or-g_navigationopenpopup" class="kg-header-card-subheading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><i><em class="italic" style="white-space: pre-wrap;">Pick one: </em></i><i><code spellcheck="false" style="white-space: pre-wrap;"><em class="italic">current</em></code></i><i><em class="italic" style="white-space: pre-wrap;"> or </em></i><i><code spellcheck="false" style="white-space: pre-wrap;"><em class="italic">g_navigation.openPopup</em></code></i></p>
                    
                </div>
            </div>
        </div><p>ServiceNow lets us add links to forms via UI Actions, which is great! What's <em>not</em> so great is that one cannot easily configure these links to open in a new tab... at least when using <em>server-side</em> UI Actions.</p><p>The current wisdom is that you <strong>need</strong> to use <em>client-side</em> UI Actions...</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">function myNewTab() {
  g_navigation.openPopup('task_list.do?sysparm_query=active%3Dtrue');
}</code></pre><figcaption><p><i><em class="italic" style="white-space: pre-wrap;">g_navigation.openPopup is a client-side API with no server-side equivalent</em></i></p></figcaption></figure><p>This is well and good until you realize that client-side UI Actions can't use <code>current</code>. Without <code>current</code>, we're forced to resort to workarounds like <code>g_scratchpad</code> or <code>GlideAjax</code>. Such workarounds impose extra complexity and make a real headache of building what <em>should be</em> simple page links.</p><p>Well... Guess what? <strong>There's a better way!</strong></p><hr><div class="kg-card kg-header-card kg-v2 kg-width-regular " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="the-solution" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">The Solution</span></h2>
                    <p id="retargetting-the-form-submission-to-_blank" class="kg-header-card-subheading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><i><em class="italic" style="white-space: pre-wrap;">Retargetting the form submission to </em></i><i><code spellcheck="false" style="white-space: pre-wrap;"><em class="italic">_blank</em></code></i></p>
                    
                </div>
            </div>
        </div><p><strong>First, a caveat: this trick is for Classic Forms only</strong>. This limitation exists because the classic UI uses HTML forms for everything, including UI Actions. HTML forms have a <code>target</code> property, which allows us to change the form submission behavior to open in a new tab.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/09/image.png" class="kg-image" alt="" loading="lazy" width="832" height="484" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/09/image.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/09/image.png 832w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">UI Action that computes the URL server-side, on-demand</em></i></figcaption></figure><p>Let's hop into it. The above example is the whole cake: It opens a new tab, runs some server-side code to compute the link, then redirects the window.</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">function openOpsView() {
	// Client-Side Code
	var form = g_form.getFormElement();
	var oldTarget = form.target;
	form.target = '_blank';
	g_form.submit('sysverb_no_update_open_approval_flow');
	form.target = oldTarget;
}

if (typeof window === 'undefined') {
	// Server-Side Code
	var baseUrl = '$flow-designer.do#/operations/context/';
	action.setRedirectURL(baseUrl + current.u_approval_context);
	action.setURLParameter('sysparm_nostack', 'true');
}</code></pre><figcaption><p><i><em class="italic" style="white-space: pre-wrap;">UI Action Script</em></i></p></figcaption></figure><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://relay.semaphorepartners.com/articles/client-server-ui-actions-without-ajax/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">UI Action Client/Server Interactions (without AJAX!)</div><div class="kg-bookmark-description">Do you hate AJAX Script Includes with a fiery passion? I do – they’re ugly, error-prone, and full of annoying boilerplate! I consider the unfortunate reliance upon AJAX Script Includes within UI Actions to be one of ServiceNow’s foundational sins. It simply should not be this hard to achieve basic user</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/icon/SP_SQ-1.png" alt=""><span class="kg-bookmark-author">Relay</span><span class="kg-bookmark-publisher">Christopher Crockett</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/thumbnail/Relay-Card-1.png" alt="" onerror="this.style.display = 'none'"></div></a><figcaption><p><i><em class="italic" style="white-space: pre-wrap;">Click here to learn more about this type of "hybrid" UI Script!</em></i></p></figcaption></figure><p><strong>Breaking down each part individually</strong>...</p><ul><li><strong>Client</strong>: Setting this true allows us to temporarily change the client-side form's <code>target</code> to <code>_blank</code>. This trick is what causes the UI Action to open a new tab when calling <code>g_form.submit</code>.</li><li><strong>Action Name</strong>: The <code>sysverb_no_update_</code> prefix skips form saving/mandatories.</li><li><strong>Script</strong>: The code within our <code>if</code> block is server-side and triggers at the moment the new tab is opening. We use it to compute the link with access to <code>current</code> and then server-side redirect the still-loading tab via <code>action.setRedirectURL</code></li></ul><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">💡</div><div class="kg-callout-text">In this example, we also apply <code spellcheck="false" style="white-space: pre-wrap;">sysparm_nostack</code>, which prevents the new tab from interfering with the main tab's back-button history.<br><br>This is only necessary for internal links. There's no need to worry about the stack when redirecting to outside websites!</div></div><hr><div class="kg-card kg-header-card kg-v2 kg-width-regular " style="background-color: #000000;" data-background-color="#000000">
            
            <div class="kg-header-card-content">
                
                <div class="kg-header-card-text kg-align-center">
                    <h2 id="takeaways" class="kg-header-card-heading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><span style="white-space: pre-wrap;">Takeaways</span></h2>
                    <p id="when-all-youve-got-is-a-hammer" class="kg-header-card-subheading" style="color: #FFFFFF;" data-text-color="#FFFFFF"><i><em class="italic" style="white-space: pre-wrap;">When all you've got is a hammer...</em></i></p>
                    
                </div>
            </div>
        </div><p><strong>This technique has a few notable downsides</strong>...</p><ul><li><strong>Synchronous</strong>: Interacting with the original tab is <strong>blocked</strong> while this runs.</li><li><strong>Classic Forms Only</strong>: Not compatible with Lists or Workspaces.</li></ul><p>That's about it, really! For more polished parts of your UX, it's still advisable to invest the requisite effort for a <code>g_scratchpad</code>-based implementation, because the blocking <em>is</em> perceptible.</p><p><strong>On the other hand</strong>: This is certainly better than <code>g_scratchpad</code> for cases where you <em>need</em> to compute links on-demand (<em>e.g. for pageload performance or special behavior</em>). The only alternative in those cases was <code>GlideAjax</code>, which was often overkill.</p><p><strong>On the <em>other</em>, other hand</strong>: <code>GlideAjax</code> is capable of calling non-blocking server-side code. As clunky as it is, <code>GlideAjax</code> still has good use for more heavy-duty interaction requirements.</p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ How to stop getting the OOB VTB Notification ]]></title>
        <description><![CDATA[ Upgrade Season means a few things: Reading release notes, reviewing skipped records, and my inbox being cluttered with &quot;VTB&quot; Notifications


What is it?

It&#39;s an OOB (out-of-box) notification on the Board Member (vtb_board_member) table, that fires on insert of any record. Since a new ]]></description>
        <link>https://relay.semaphorepartners.com/articles/how-to-stop-getting-the-oob-vtb-notification/</link>
        <guid isPermaLink="false">68d150bd482c8400011d71ba</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Mon, 22 Sep 2025 10:53:18 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/09/Screenshot-2025-09-22-at-10.56.09---AM.png" medium="image"/>
        <content:encoded><![CDATA[ <p>Upgrade Season means a few things: Reading release notes, reviewing skipped records, and my inbox being cluttered with "VTB" Notifications</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/09/image-3.png" class="kg-image" alt="" loading="lazy" width="2000" height="1892" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/09/image-3.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/09/image-3.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2025/09/image-3.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w2400/2025/09/image-3.png 2400w" sizes="(min-width: 720px) 720px"></figure><h2 id="what-is-it">What is it?</h2><p>It's an OOB (out-of-box) notification on the Board Member (vtb_board_member) table, that fires on insert of any record. Since a new visual task board is created for each upgrade [<a href="https://www.servicenow.com/docs/bundle/zurich-platform-administration/page/administer/upgrade-management/concept/um-vtb-history.html?ref=relay.semaphorepartners.com">Skipped Records visual task board (VTB)</a><a href="https://www.servicenow.com/docs/bundle/zurich-platform-administration/page/administer/upgrade-management/task/um-resolve-skipped-update.html?ref=relay.semaphorepartners.com"></a><a href="https://www.servicenow.com/docs/bundle/zurich-platform-administration/page/administer/upgrade-management/reference/um-history-task-form.html?ref=relay.semaphorepartners.com"></a>], all admins will receive a notification for every upgrade, patches or hotfix. </p><h2 id="is-it-that-annoying">Is it that annoying?</h2><p>No, not really. It's pretty easy to delete an email a few times year. BUT it's been a lot of years (almost 7), and multiply that by a dozen clients, and it ends up being a lot of junk in my inbox (see screenshot). </p><h2 id="how-to-turn-it-off">How to turn it off</h2><p>Here is the notification itself: /sysevent_email_action.do?sys_id=81e12044b7332300bf6b68a9ee11a91d</p><p>You can </p><ol><li>Turn it off (active = false) or</li><li>Add a condition to turn it off for certain boards ex: board.nameNOT LIKEPatch</li></ol> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Using the new fetch API in background scripts ]]></title>
        <description><![CDATA[ The fetch API was added to ServiceNow in the Yokohama release for use in background scripting and, indeed, this is reflected in the official docs:

💬You can use the Fetch API in a background script and wherever you can make HTTP calls (like a REST endpoint).
- ServiceNow Zurich Documentation, ]]></description>
        <link>https://relay.semaphorepartners.com/articles/using-the-new-fetch-api-in-background-scripts/</link>
        <guid isPermaLink="false">68bf327c02966a000185639d</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Christopher Crockett ]]></dc:creator>
        <pubDate>Tue, 09 Sep 2025 12:12:14 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/09/fetch.png" medium="image"/>
        <content:encoded><![CDATA[ <p>The <code>fetch</code> API was added to ServiceNow in the Yokohama release for use in background scripting and, indeed, this is reflected in the official docs:</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">💬</div><div class="kg-callout-text"><i><em class="italic" style="white-space: pre-wrap;">You can use the&nbsp;Fetch&nbsp;API </em></i><i><b><strong class="italic" style="white-space: pre-wrap;">in a background script</strong></b></i><i><em class="italic" style="white-space: pre-wrap;"> and wherever you can make HTTP calls (like a REST endpoint).</em></i><br>- ServiceNow Zurich Documentation, September 2025</div></div><p>Unfortunately, the usage examples for <em>how</em> to use the API are (<em>at time of writing</em>) factually inaccurate. The examples given only work as-is within ES Modules and will fail if attempted under any other background scripting context.</p><figure class="kg-card kg-code-card"><pre><code>Script execution error: Script Identifier: null.null.script, Error Description: "fetch" is not defined., Script ES Level: 200
Evaluator: com.glide.script.RhinoEcmaError: "fetch" is not defined.
   script : Line(1) column(0)
==&gt;   1: fetch('google.com');
 Stack trace:
	at null.null.script:1 </code></pre><figcaption><p><i><em class="italic" style="white-space: pre-wrap;">The result of calling </em></i><i><code spellcheck="false" style="white-space: pre-wrap;"><em class="italic">fetch('google.com')</em></code></i><i><em class="italic" style="white-space: pre-wrap;"> in a background script</em></i></p></figcaption></figure><hr><p><strong>Instead, the correct way to access <code>fetch</code> and associated APIs is as follows</strong>:</p><pre><code class="language-javascript">const FetchApis = require('servicenow:fetch');
const response = await FetchApis.fetch('google.com');</code></pre><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">❗</div><div class="kg-callout-text"><code spellcheck="false" style="white-space: pre-wrap;">require</code> is only available in scoped applications! This means you'll be unable to directly use <code spellcheck="false" style="white-space: pre-wrap;">fetch</code> in the global scope.<br><br>It is possible to work around this issue by creating a Scoped App and Script Include (<i><em class="italic" style="white-space: pre-wrap;">w/ Accessible From = "all application scopes"</em></i>) like so:<br><code spellcheck="false" style="white-space: pre-wrap;">const MyScriptInclude = require('servicenow:fetch');</code></div></div><p>As far as we currently know, this is the only such case where APIs are gated behind the <code>servicenow:</code> prefix... though the creation of this new convention certainly implies more to eventually come.</p><hr><p>We expect that the documentation will eventually be corrected to reflect the actual usage, since the current incarnation clearly implies an intent to support...<br><br><strong>... For the time being, here's an unofficial list of what's in <code>servicenow:fetch</code></strong>:</p><ul><li><code>fetch</code>: <a href="https://www.servicenow.com/docs/csh?topicname=FetchAPI.html&version=latest&ref=relay.semaphorepartners.com">ServiceNow</a> / <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch?ref=relay.semaphorepartners.com">MDN</a></li><li><code>Headers</code>: <a href="https://www.servicenow.com/docs/csh?topicname=Fetch.HeadersAPI.html&version=latest&ref=relay.semaphorepartners.com">ServiceNow</a> / <a href="https://developer.mozilla.org/en-US/docs/Web/API/Headers?ref=relay.semaphorepartners.com">MDN</a></li><li><code>Request</code>: <a href="https://www.servicenow.com/docs/csh?topicname=Fetch.RequestAPI.html&version=latest&ref=relay.semaphorepartners.com">ServiceNow</a> / <a href="https://developer.mozilla.org/en-US/docs/Web/API/Request?ref=relay.semaphorepartners.com">MDN</a></li><li><code>Response</code>: <a href="https://www.servicenow.com/docs/csh?topicname=Fetch.ResponseAPI.html&version=latest&ref=relay.semaphorepartners.com">ServiceNow</a> / <a href="https://developer.mozilla.org/en-US/docs/Web/API/Response?ref=relay.semaphorepartners.com">MDN</a></li><li><code>AbortController</code>: <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController?ref=relay.semaphorepartners.com">MDN</a></li><li><code>AbortSignal</code>: <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal?ref=relay.semaphorepartners.com">MDN</a></li><li><code>File</code>: <a href="https://developer.mozilla.org/en-US/docs/Web/API/File?ref=relay.semaphorepartners.com">MDN</a></li><li><code>FormData</code>: <a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData?ref=relay.semaphorepartners.com">MDN</a></li><li><code>Blob</code>: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob?ref=relay.semaphorepartners.com">MDN</a></li><li><code>ReadableStream</code>: <a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream?ref=relay.semaphorepartners.com">MDN</a></li><li><code>ReadableStreamReader</code>: <a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader?ref=relay.semaphorepartners.com">MDN</a></li><li><code>TextDecoder</code>: <a href="https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder?ref=relay.semaphorepartners.com">MDN</a></li><li><code>TextEncoder</code>: <a href="https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder?ref=relay.semaphorepartners.com">MDN</a></li><li><code>URL</code>: <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL?ref=relay.semaphorepartners.com">MDN</a></li><li><code>URLSearchParams</code>: <a href="https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams?ref=relay.semaphorepartners.com">MDN</a></li></ul><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">🤨</div><div class="kg-callout-text">Yes, really... <i><em class="italic" style="white-space: pre-wrap;">this</em></i> is how we finally got a proper <code spellcheck="false" style="white-space: pre-wrap;">URL</code> manipulation class.<br>As an undocumented export of an (<i><em class="italic" style="white-space: pre-wrap;">accidentally</em></i>) undocumented module.</div></div> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ When APIs Don&#x27;t Exist, We Make Our Own: A Fantasy Football Data Integration Story ]]></title>
        <description><![CDATA[ How reverse engineering ESPN&#39;s fantasy football API taught me that determination beats documentation every time


The Problem: Custom League, Custom Problems

It&#39;s August, which means one thing in my household: fantasy football draft preparation is in full swing. But here&#39;s the thing about our ]]></description>
        <link>https://relay.semaphorepartners.com/articles/when-apis-dont-exist-we-make-our-own-a-fantasy-football-data-integration-story/</link>
        <guid isPermaLink="false">68a73d69857461000156c770</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Thu, 21 Aug 2025 12:07:49 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/08/Screenshot-2025-08-21-at-11.53.48---AM.png" medium="image"/>
        <content:encoded><![CDATA[ <p><em>How reverse engineering ESPN's fantasy football API taught me that determination beats documentation every time</em></p><h2 id="the-problem-custom-league-custom-problems">The Problem: Custom League, Custom Problems</h2><p>It's August, which means one thing in my household: fantasy football draft preparation is in full swing. But here's the thing about our keeper league – it's beautifully complex and frustratingly unique. We use custom scoring rules (6-point passing TDs, no PPR), we can keep players from previous seasons, and ESPN's native app doesn't let us designate keepers properly.</p><p>Standard fantasy rankings? Useless. Third-party sites? They don't account for our scoring quirks. ESPN's export features? Non-existent for what we need.</p><p>As someone who works at Semaphore Partners implementing ServiceNow solutions, I've learned that when you can't find the integration you need, you build it yourself. Even if that means getting creative with undocumented APIs.</p><h2 id="the-solution-when-theres-no-api-become-the-api-whisperer">The Solution: When There's No API, Become the API Whisperer</h2><h3 id="step-1-network-tab-detective-work">Step 1: Network Tab Detective Work</h3><p>My first move was opening Chrome's Developer Tools and watching what happened when I loaded ESPN's fantasy rankings. After some digging through the Network tab, I discovered the goldmine:</p><pre><code>GET: https://lm-api-reads.fantasy.espn.com/apis/v3/games/ffl/seasons/2025/segments/0/leagues/${LEAGUE_ID}
</code></pre><p>The magic was in the headers – specifically this beautifully complex filter:</p><pre><code class="language-json">X-Fantasy-Filter: {
  "players": {
    "filterStatus": {"value": ["FREEAGENT","WAIVERS"]},
    "filterSlotIds": {"value": [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,23,24]},
    "filterRanksForScoringPeriodIds": {"value": [1]},
    "sortPercOwned": {"sortPriority": 2, "sortAsc": false},
    "sortDraftRanks": {"sortPriority": 100, "sortAsc": true, "value": "STANDARD"},
    "limit": 300
  }
}</code></pre><p>You'll also need to copy a cookie from your browser as well, for authentication.</p><p>This gives me exactly what I need: all available players with their rankings, ownership percentages, and projected points – all adjusted for our league's specific scoring system.</p><h3 id="step-2-api-testing-with-rapidapi">Step 2: API Testing with RapidAPI</h3><p>Rather than building a full application, I use RapidAPI (or Paw) to make these calls manually. It's perfect for one-off data pulls and lets me experiment with different filter parameters without writing code.</p><p>The response comes back as rich JSON data with everything I need:</p><pre><code>{
  "players": [
    {
    "fullName": "Ja'Marr Chase",
    "defaultPositionId": 3,
    "proTeamId": 4,
    "draftRanksByRankType": {
      "STANDARD": {
        "rank": 1,
        "auctionValue": 57
      }
    },
    "ownership": {
      "averageDraftPosition": 1.56,
      "percentOwned": 99.92
    }
  }
  ...</code></pre><h3 id="step-3-browser-console-magic">Step 3: Browser Console Magic</h3><p>Here's where it gets fun. Rather than writing a separate script, I use the browser console to transform the JSON into tab-delimited data which can easily be pasted into Google Sheets:</p><pre><code class="language-javascript">copy(
  o.players.map(function(x) {
    return [
      x.player.fullName,
      x.player.defaultPositionId,
      x.player.proTeamId,
      x.player.draftRanksByRankType.STANDARD.rank,
      "",
      x.player.ownership.averageDraftPosition,
      x.player.ownership.averageDraftPositionPercentChange,
      x.player.stats[x.player.stats.length-1].appliedTotal
    ].join("\t")
  }).join("\n")
)</code></pre><p>This one-liner extracts player name, position, team, rank, ADP, and projected points – perfectly formatted for pasting directly into Google Sheets.</p><h3 id="step-4-google-sheets-as-the-ultimate-dashboard">Step 4: Google Sheets as the Ultimate Dashboard</h3><p>The final piece is my Google Sheets setup. I paste the data and use conditional formatting to:</p><ul><li>Color-code different positions (WRs in blue, RBs in green, etc.)</li><li>Highlight players who are already kept (so I know they're off-limits)</li><li>Create separate columns for each position<ul><li>ex: =QUERY('2025 Raw Data'!A2:J301,"SELECT D, A, J, H, F WHERE I='RB'", -1)</li></ul></li></ul><p>The result? A custom fantasy football dashboard that accounts for our unique league settings and keeper situation.</p><figure class="kg-card kg-image-card kg-width-full"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/08/image-4.png" class="kg-image" alt="" loading="lazy" width="1458" height="642" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/08/image-4.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/08/image-4.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/08/image-4.png 1458w"></figure><h2 id="the-bigger-picture-integration-philosophy">The Bigger Picture: Integration Philosophy</h2><p>This little project perfectly illustrates something we live by at Semaphore Partners: <strong>there's always a way to integrate, even when there isn't supposed to be one.</strong></p><p>Whether it's:</p><ul><li>Legacy systems without modern APIs</li><li>SaaS platforms that don't officially support your use case</li><li>Third-party services with limited export capabilities</li></ul><p>The key is being stubborn, determined, and skilled enough to find the data pathways that exist – even if they're not documented or officially supported.</p><h2 id="the-technical-lessons">The Technical Lessons</h2><ol><li><strong>Network inspection is your friend</strong> – Most modern web apps are API-driven, even if those APIs aren't public</li><li><strong>Browser dev tools are incredibly powerful</strong> – You can do sophisticated data transformation without leaving the browser</li><li><strong>Don't over-engineer</strong> – Sometimes a manual process with the right tools beats a complex automated solution</li><li><strong>Google Sheets is more powerful than people think</strong> – It's often the perfect middle layer between raw data and actionable insights</li></ol><h2 id="the-fantasy-football-confession">The Fantasy Football Confession</h2><p>Has this elaborate data pipeline helped me win my fantasy league?</p><p>Absolutely not. In fact, I have finished dead last more often that I would like to admit. In 2015, I even had to take the SATs (as a 25 year old) as punishment. </p><p>But this is my year. I can feel it.</p><p><em>And if it's not, well, at least I'll have the most beautifully formatted last-place roster in fantasy football history.</em></p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ How to import a file from your MID Server in 2 Flow Designer Steps! ]]></title>
        <description><![CDATA[ The Challenge: When APIs Cost Too Much

Picture this: you need to import data from a third-party system, but their API costs $25,000 annually. Meanwhile, they&#39;ll let you export the same data for free as a file. What option would you choose?

This exact scenario recently came ]]></description>
        <link>https://relay.semaphorepartners.com/articles/how-to-import-a-file-from-your-mid-server-in-2-flow-designer-steps/</link>
        <guid isPermaLink="false">68936d334c7a660001d47967</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Tue, 12 Aug 2025 09:48:03 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/08/MID-Server.png" medium="image"/>
        <content:encoded><![CDATA[ <p></p><h2 id="the-challenge-when-apis-cost-too-much">The Challenge: When APIs Cost Too Much</h2><p>Picture this: you need to import data from a third-party system, but their API costs $25,000 annually. Meanwhile, they'll let you export the same data for free as a file. What option would you choose?</p><p>This exact scenario recently came up with a client using Concur. While we'd normally build a sleek API integration (and trust us, we love integrations at Semaphore Partners), the astronomical API pricing forced us to get creative. The client could only drop files on their MID Server, so we needed a solution that worked within those constraints.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/08/image-2.png" class="kg-image" alt="" loading="lazy" width="697" height="350" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/08/image-2.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/08/image-2.png 697w"></figure><h2 id="a-bit-of-history"><br>A Bit of History</h2><p>Back in 2014, there was a fantastic app called Remote File Importer on ServiceNow Share (thank you to <a href="https://servicenowguru.com/gurus/?ref=relay.semaphorepartners.com">SNC Guru</a> <a href="https://www.linkedin.com/in/markstanger/?ref=relay.semaphorepartners.com">Mark Stanger</a>!). It solved this exact problem, but ServiceNow Share is no longer active, and finding those old update sets is like searching for buried treasure.</p><p>The good news? ServiceNow's newer capabilities make this easier than ever.</p><h2 id="our-solution-a-two-step-flow">Our Solution: A Two-Step Flow</h2><p>We created a custom Flow Action that grabs files from the MID Server and attaches them to a Data Source in ServiceNow. Once attached, the Data Source treats it like any manually uploaded file and imports it seamlessly.</p><p>Here's how it works:</p><h3 id="step-1-mid-server-script">Step 1: MID Server Script</h3><p>The first step runs a script on the MID Server to locate and read the target file. This step handles all the file system operations and returns the file's base64 encoded content to ServiceNow.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/08/image.png" class="kg-image" alt="" loading="lazy" width="1788" height="532" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/08/image.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/08/image.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2025/08/image.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/08/image.png 1788w" sizes="(min-width: 720px) 720px"></figure><pre><code class="language-javascript">(function execute(inputs, outputs) {
  function getMimeType(extension) {
    switch (extension) {
      case 'jpg':
      case 'jpeg':
        return 'image/jpeg';
      case 'png':
        return 'image/png';
      case 'gif':
        return 'image/gif';
      case 'xlsx':
        return 'application/xlsx';
      default:
        return 'application/octet-stream';
    }
  }
  
  //const file = new Packages.java.io.File(inputs.filePath);
  const file = new Packages.java.io.File(
    inputs.file_path+"\\"+inputs.file_name
  );

  var result = [];

  // Read file and encode to base64
  var fileInputStream = null;
  var base64Data = "";

  var skipReason = "";
  var fileName = "";
  var fileSize = 0;
  var fileExtension = "";
  var fileMimeType = "";
  var fileLastModified = "";
  var base64Data = "";

  // Get basic file info even if we skip it
  try {
    fileName = file.getName() + "";
    fileSize = file.length();
    fileExtension =
      fileName.indexOf(".") !== -1
        ? fileName.split(".").pop().toLowerCase()
        : "";
    fileMimeType = getMimeType(fileExtension);
    fileLastModified = file.lastModified();
  } catch (infoError) {
    skipReason = "Unable to read file information";
  }

  try {
    fileInputStream = new Packages.java.io.FileInputStream(file);
    const bytes = new Packages.java.lang.reflect.Array.newInstance(
      Packages.java.lang.Byte.TYPE,
      fileSize
    );

    fileInputStream.read(bytes);
    base64Data = Packages.java.util.Base64.getEncoder().encodeToString(bytes);
  } finally {
    if (fileInputStream) {
      fileInputStream.close();
    }
  }
   outputs.result = JSON.stringify({
      fileName: fileName,
      fileSize: fileSize,
      fileExtension: fileExtension,
      fileMimeType: fileMimeType,
      fileLastModified: fileLastModified,
      base64Data: base64Data
    });
})(inputs, outputs);
</code></pre><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/08/image-1.png" class="kg-image" alt="" loading="lazy" width="1726" height="434" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/08/image-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/08/image-1.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2025/08/image-1.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/08/image-1.png 1726w" sizes="(min-width: 720px) 720px"></figure><h3 id="step-2-server-script">Step 2: Server Script</h3><p>The second step takes the file content from Step 1 and creates an attachment on your Data Source using server-side code. This transforms the raw file data into a properly formatted ServiceNow attachment.</p>

<aside data-content-cta class="relative js-toc-ignore">
        <div data-content-cta-blur class="absolute w-full top-0 -translate-y-full bg-gradient-to-b to-70% from-transparent to-white dark:to-gray-900">
            &nbsp; <br> &nbsp; <br> &nbsp;
        </div>

    <div class="flex flex-col items-center text-center w-full px-4 py-10 lg:px-10 text-base bg-accent-10 rounded-md dark:border dark:border-accent-20">
        <h2 class="text-gray-900 dark:text-gray-50 text-xl sm:text-2xl leading-snug tracking-tight">
                    This post is for subscribers only
        </h2>

            <button data-portal="signup" class="bg-accent text-accent-contrast rounded-lg px-3 py-1.5 mt-7 font-semibold text-base leading-relaxed hover:opacity-85">
                Subscribe now
            </button>
            <p class="mt-5 text-base">
                Already have an account? <button data-portal="signin" class="block sm:inline mx-auto font-medium underline decoration-accent decoration-2 underline-offset-2 hover:text-accent">Sign in</button>
            </p>
    </div>
</aside>
 ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Prefill Catalog Items from URL [Zurich+] ]]></title>
        <description><![CDATA[ Starting in Zurich, prefilling catalog items via URL parameters no longer requires a client script workaround. This is now a native feature that works seamlessly in both Service Portal and Next Experience.

The ServiceNow documentation includes a subtle mention of this functionality:

❓Help requesters complete catalog item forms faster on ]]></description>
        <link>https://relay.semaphorepartners.com/articles/prefill-catalog-items-from-url-zurich/</link>
        <guid isPermaLink="false">68893a7210278b00019fc8b1</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Tue, 05 Aug 2025 09:49:15 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/08/Catalog_URL.png" medium="image"/>
        <content:encoded><![CDATA[ <p>Starting in Zurich, prefilling catalog items via URL parameters no longer requires a client script workaround. This is now a native feature that works seamlessly in both Service Portal and Next Experience.</p><p>The ServiceNow <a href="https://www.servicenow.com/docs/bundle/zurich-release-notes/page/release-notes/now-platform-capabilities/service-catalog-rn.html?ref=relay.semaphorepartners.com" rel="noreferrer">documentation</a> includes a subtle mention of this functionality:</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">❓</div><div class="kg-callout-text">Help requesters complete catalog item forms faster on portals and Next Experience UIs using caller-provided key-value pairs that pre-fill catalog item forms.</div></div><p>But what does that actually mean, How do I use it, and how does it work?</p><h3 id="how-to-use-it">How to use it:</h3><p>Within your standard Service Portal URLs insert a <code>sysparm_variable_values=</code> then include your variable values as a JSON string.</p><p>For example:</p><p>Let's use the "<strong>Standard Laptop</strong>" demo data Catalog Item</p><pre><code>/esc?id=sc_cat_item&amp;sys_id=04b7e94b4f7b4200086eeed18110c7fd
&amp;sysparm_variable_values={"Additional_software_requirements":"Follow Semaphore Partners on Linkedin","acrobat":"true"}</code></pre><h3 id="how-it-works">How it works:</h3><p>Most of the code lives in the Service Portal Widget: "SC Catalog Item" (widget-sc-cat-item-v2).</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">⚠️</div><div class="kg-callout-text"><b><strong style="white-space: pre-wrap;">Important</strong></b>: Ensure you're using the correct baseline widget on your Service Portal page.</div></div><p>Starting from line 346 (there are other code blocks, but this is the most important)</p><pre><code class="language-javascript">if (options.disable_url_prefill == "false" &amp;&amp; gs.getProperty(ServiceCatalogConstantsSNC.PROPERTIES.SC_ENABLE_URL_PREFILL, "true") == "true") {
    var paramValues = $sp.getParameter("sysparm_variable_values");
    if (paramValues != null) {
        var variables = {};
        try {
            variables = JSON.parse(paramValues);
        } catch (e) {
            // it can be a tinyId
            variables = sn_sc.CatalogVariableUtil.getVariablesFromTiny(paramValues);
        }
        data.variableValueMap = variables;
        populateDisplayValues();
    }
}
</code></pre><p>The code includes two primary controls:	</p><p>• <code>options.disable_url_prefill</code> from the options schema	</p><p>• The constant <code>ServiceCatalogConstantsSNC.PROPERTIES.SC_ENABLE_URL_PREFILL</code>, which points to the system property <code>glide.sc.enable_url_prefill</code></p><p>While the ServiceNow <a href="https://www.servicenow.com/docs/bundle/zurich-servicenow-platform/page/product/service-catalog-management/task/t_CreateAVariableForACatalogItem.html?ref=relay.semaphorepartners.com" rel="noreferrer">documentation</a> mentions controlling this on a variable basis via "Disable initial slot fill," this option doesn't appear to be available as of the GA release of Zurich.</p><h3 id="practical-use-cases">Practical Use Cases</h3><p>This feature enables several valuable scenarios:</p><p>•<strong>Workspace Integration:</strong> Prefilling via UI Actions in Workspaces</p><p>•<strong>Email Communications:</strong> Sending links with key fields already populated</p><p>•<strong>User Convenience:</strong> Bookmarking frequently submitted forms with default values</p><p>•<strong>Portals/ KB:</strong> Embedding catalog requests in portal links or knowledge base articles	</p><p>•<strong>Development &amp; Testing:</strong> Simplifying testing during development or demos</p><h3 id="important-considerations">Important Considerations</h3><p>Since this functionality is enabled by default in Zurich, consider the following:</p><ol><li><strong>Remove Legacy Code: </strong>Eliminate your legacy catalog client scripts—there should be no reason to maintain this legacy code any longer</li><li><strong>Security Review</strong>: Ensure you're comfortable with this functionality, as it's now more broadly available compared to previous versions where it was more isolated to specific forms/variables</li><li><strong>Widget Verification</strong>: Use the baseline Service Portal Widget "SC Catalog Item" (widget-sc-cat-item-v2) if you had previously cloned it</li></ol> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ What can the Contextual Side Panel actually do? (in HR Agent Workspace) ]]></title>
        <description><![CDATA[ Within the HR Agent Workspace, the contextual side panel can be a huge help to closing cases efficiently, and training up newer agents. However, some aspects of it can be redundant, confusing and redundant. Internally, we have a handy guide to help illustrate which pieces can do what.

Although ServiceNow ]]></description>
        <link>https://relay.semaphorepartners.com/articles/what-can-the-contextual-side-panel-actually-do-in-hr-agent-workspace/</link>
        <guid isPermaLink="false">6876b4c4d6a9f30001cfb5de</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Thu, 24 Jul 2025 09:18:13 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/07/Contextual-Side-Panel-1.png" medium="image"/>
        <content:encoded><![CDATA[ <p>Within the HR Agent Workspace, the contextual side panel can be a huge help to closing cases efficiently, and training up newer agents. However, some aspects of it can be redundant, confusing and redundant. Internally, we have a handy guide to help illustrate which pieces can do what. </p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/07/contextual-sidebar-1-1.png" class="kg-image" alt="" loading="lazy" width="742" height="740" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/07/contextual-sidebar-1-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/07/contextual-sidebar-1-1.png 742w" sizes="(min-width: 720px) 720px"></figure><p>Although ServiceNow has some documentation on the Side Panel (<a href="https://www.servicenow.com/docs/bundle/yokohama-employee-service-management/page/product/human-resources/concept/agent-ws-hr-case-mgmt-context-sidebar.html?ref=relay.semaphorepartners.com">seen here</a>), it can be helpful to have everything in one spot. </p><p>Let us know if there's anything we missed!</p><figure class="kg-card kg-image-card kg-width-full"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/07/image-6.png" class="kg-image" alt="" loading="lazy" width="2000" height="660" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/07/image-6.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/07/image-6.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2025/07/image-6.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w2400/2025/07/image-6.png 2400w"></figure><p>Each client may not use every piece of this, but it is important to ensure that you're using the right tool for the job. For example, if they don't need attachments, use <strong>Response Templates</strong> instead of <strong>Email Client Templates. </strong>If a client isn't using the Knowledge Base, then <strong>Agent Assist</strong> or <strong>Fulfillment Instructions</strong> may not be the best option. </p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Filters, Encoded Queries, and Breadcrumbs: A ServiceNow Fairy Tale ]]></title>
        <description><![CDATA[ Once upon a time, in the Brothers Grimm fairy tale &quot;Hansel and Gretel,&quot; two children dropped breadcrumbs along their forest path to find their way home. While their breadcrumbs were eaten by birds (spoiler alert!), this simple concept of leaving a trail to navigate back through complex terrain ]]></description>
        <link>https://relay.semaphorepartners.com/articles/filters-encoded-queries-and-breadcrumbs-a-servicenow-fairy-tale/</link>
        <guid isPermaLink="false">6802484343bed60001f1276f</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Wed, 04 Jun 2025 09:22:12 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/06/filters_breadcrumbs.png" medium="image"/>
        <content:encoded><![CDATA[ <p>Once upon a time, in the Brothers Grimm fairy tale "Hansel and Gretel," two children dropped breadcrumbs along their forest path to find their way home. While their breadcrumbs were eaten by birds (spoiler alert!), this simple concept of leaving a trail to navigate back through complex terrain has become a cornerstone of modern web interfaces. Today's digital breadcrumbs help users understand where they are and how they got there, transforming from fairy tale navigation aid to essential UX element.</p><p>In the enchanted forest of ServiceNow, filters and breadcrumbs serve a similar magical purpose—they're fundamental building blocks that power everything from lists and reports to templates, flows, and notifications. In this data-driven kingdom, filter conditions enable us to intelligently leverage attributes from across the platform, helping us navigate through complex data relationships just like breadcrumbs guide travelers through dark woods.</p><h2 id="the-foundation-encoded-query-syntaxour-magic-spell">The Foundation: Encoded Query Syntax - Our Magic Spell</h2><p>At the heart of ServiceNow's filtering capabilities lies the encoded query syntax—our magic spell for data manipulation. This powerful incantation can be cast in multiple contexts, from URLs to GlideRecords, making it an essential tool for any ServiceNow wizard.</p><h3 id="applyencodedquery-the-enchanted-configuration">applyEncodedQuery: The Enchanted Configuration</h3><p>One of my favorite spells when conjuring ServiceNow functionality is the <code>applyEncodedQuery</code> method:</p><pre><code class="language-javascript">// Once upon a time, in an enchanted forest...
var forestRecord = new GlideRecord('enchanted_forest');
forestRecord.applyEncodedQuery('creatures=giants^realm.name=Fairyland^magical_protection=true');

// Our brave adventurer seeks the golden castle
var questLog = new GlideRecord('hero_quest');
questLog.applyEncodedQuery('destination=golden_castle^companions=breadcrumb_trail^status=active');
</code></pre><p>This magical example demonstrates how <code>applyEncodedQuery</code> can <strong>set values</strong> on records directly, much like casting a spell to transform the very nature of our data. This approach is invaluable for configuration purposes—it allows the wise village elders (application owners) to modify quest parameters without exposing them to the arcane code beneath, creating a cleaner separation between the magic and its manifestation.</p><h2 id="glidefilter-the-wise-oracles-clean-decisions">GlideFilter: The Wise Oracle's Clean Decisions</h2><p>GlideFilter often provides a <strong>much cleaner</strong> alternative to messy <code>if</code> conditions scattered throughout our ServiceNow kingdom, acting like a wise oracle that makes clear decisions. The magical benefits are substantial:</p><ul><li>Conditions can be written on ancient scrolls (system properties) or carved into stone tablets (table configurations)</li><li>Our spells become more readable and easier for future wizards to maintain</li><li>Provides flexible handling of case sensitivity and null values—even works with incomplete incantations</li><li>Separates the wisdom of business rules from the mechanics of their implementation</li></ul><pre><code class="language-javascript">// The oracle speaks in clear, encoded wisdom
var ancientWisdom = 'creature_type=helpful_fairy^location.realm=enchanted_forest^trust_level=high';
var oracle = new GlideFilter(magicalCondition, ancientWisdom);

// Ask the oracle: "Should we trust this forest creature?"
if (oracle.match(currentCreatureRecord)) {
    // The path is safe, continue the journey
    followTheTrail();
}
</code></pre><h2 id="glidequerybreadcrumbs-the-hero-of-our-digital-fairy-tale">GlideQueryBreadcrumbs: The Hero of Our Digital Fairy Tale</h2><p>Last but certainly not least is the least documented character in our story: GlideQueryBreadcrumbs—truly the hero of our digital fairy tale! While we can gather magical conditions from ancient scrolls (system properties), stone tablets (table definitions), or other mystical configuration areas, the real challenge lies in presenting these cryptic runes to the common folk through user-friendly interfaces like Service Portal, UI Pages, or message ravens (email notifications).</p><p>You could forge a custom parser to decode these magical runes, but such a tool would fail when encountering sys_ids (unique magical identifiers) or time-based enchantments (complex date formats). This is where our hero enters the story, ready to save the day.</p><pre><code class="language-javascript">// A cryptic message left by a previous traveler
var mysteriousMessage = 'helpful_guide=a8f98bb0eb32010045e1a5115206fe3a^quest_active=true^last_seen&gt;2024-01-01';

// Our hero transforms the cryptic runes into readable wisdom
var heroicTranslator = new GlideQueryBreadcrumbs();
var readableStory = heroicTranslator.getReadableQuery('traveler_log', mysteriousMessage);
gs.info(readableStory);

// Output: "Helpful Guide is Fairy Godmother AND Quest Active is true AND Last Seen is after January 1, 2024"
</code></pre><p>This heroic utility transforms cryptic encoded queries into human-readable breadcrumb trails, automatically resolving magical references and formatting time-based enchantments appropriately for display. Unlike Hansel and Gretel's breadcrumbs, these digital ones don't get eaten by birds—they persist to guide future travelers through the same data forest.</p><h2 id="the-moral-of-our-story">The Moral of Our Story</h2><p>These three magical components work together to create a robust, maintainable approach to data navigation in the ServiceNow kingdom, much like the best fairy tales teach us important lessons:</p><ol><li><strong>Encoded queries</strong> provide the ancient language of data magic</li><li><strong>GlideFilter</strong> serves as our wise oracle for clean, configurable decision-making</li><li><strong>GlideQueryBreadcrumbs</strong> acts as our heroic translator, bridging cryptic technical runes with user-friendly breadcrumb trails</li></ol><p>By mastering these enchanted tools, you can build more flexible, maintainable, and user-friendly ServiceNow applications that separate the magic from its configuration, while ensuring that no user ever gets lost in the dark forest of complex data relationships.</p><p>And unlike the original fairy tale, these breadcrumbs will never be eaten by birds—they'll always be there to guide you home, no matter how deep into the ServiceNow forest you venture.</p><p><em>And they all lived efficiently ever after... at least until the next release.</em></p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ One studio to rule them all ]]></title>
        <description><![CDATA[ Knowledge 2025 was full of excitement and energy and, of course, a lot of talk and what is to come.  I wanted to share my favorite lab at Knowledge this year (and believe it or not it actually did not have anything to do with AI.) There is a brand ]]></description>
        <link>https://relay.semaphorepartners.com/articles/one-studio-to-rule-them-all/</link>
        <guid isPermaLink="false">682b57d8107819000155ecb4</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Brian Luchies ]]></dc:creator>
        <pubDate>Mon, 02 Jun 2025 10:06:39 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/05/Add-a-heading.png" medium="image"/>
        <content:encoded><![CDATA[ <p>Knowledge 2025 was full of excitement and energy and, of course, a lot of talk and what is to come.&nbsp; I wanted to share my favorite lab at Knowledge this year (and believe it or not it actually did not have anything to do with AI.) There is a brand new ServiceNow Studio available starting with Xanadu patch 3. One amazing feature is the ability to search scripts on the platform with ease.&nbsp; Yes, you heard that right, you can even specify if you want to only search script includes for example, or maybe only business rules and you can specify a single scope or search all.&nbsp; Any developer can see the power here when you are using reusable code and want to make sure you understand everything that would be impacted, for example, if you make changes to a function in a script include.&nbsp; You might use the same script include function in business rules, script actions, flow, and client scripts. It’s hard to keep track over time and this helps a ton!</p><p>Another amazing feature and my personal favorite is the ability to make updates in multiple scopes without making a huge mess in update sets. Using ServiceNow Studio you can set bookmarks while working on a project from any application scope and have them all in one place to work from.&nbsp; The beautiful thing about this is when you change from one configuration file to another in this one location your application scope and update set will change with the record you are updating.&nbsp;For example, if you are working on a CSM project and making updates in Global, Customer Service, and Customer Project Management applications, when in ServiceNow Studio whichever application file you are configuring the update set and scope will automatically change for you based on the last update set you used in the application scope the configuration is in!</p><p>I was pleasantly surprised by how easy it was in just one lab session to feel comfortable changing how I do my development work.&nbsp; I am quite excited to see what improvements are on the way for this in the future!&nbsp; I feel quite confident this is something that is not only here to stay but likely will be where developers work from now on.&nbsp; Give it a try, you won’t regret it!</p><p>Here are some screenshots that show these two great features:</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/05/data-src-image-4bd00dac-98c3-4c65-a127-3d566e1e573b.png" class="kg-image" alt="A screenshot of a computer

AI-generated content may be incorrect." loading="lazy" width="480" height="467"></figure><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/05/data-src-image-b9549d62-7519-422f-b45f-7e235b80150d.png" class="kg-image" alt="A screenshot of a computer

AI-generated content may be incorrect." loading="lazy" width="603" height="316" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/05/data-src-image-b9549d62-7519-422f-b45f-7e235b80150d.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/05/data-src-image-b9549d62-7519-422f-b45f-7e235b80150d.png 603w"></figure><p>&nbsp;</p><p>Change tabs within ServiceNow Studio and your scope/update set changes for you!</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/05/data-src-image-9b6b4b5e-43f9-49ee-a950-584f2c803714.png" class="kg-image" alt="A screenshot of a computer

AI-generated content may be incorrect." loading="lazy" width="495" height="169"></figure><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/05/data-src-image-b002b210-7555-4bb9-9155-1012a289e031.png" class="kg-image" alt="A screenshot of a computer

AI-generated content may be incorrect." loading="lazy" width="529" height="166"></figure><p>&nbsp;</p><p>Things to note:</p><p>·&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; This is very new so there are some things that can be a little buggy. I had a tab with the script include that wasn’t letting me search the script include like you normally can but refreshing my browser fixed it.</p><p>·&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; The only way to set bookmarks is to search for the file in the left nav and bookmark it or click on “Recent” and add them to your bookmarks from there. Hopefully at some point there will be a bookmark flag on each tab within ServiceNow Studio that you can add as a bookmark.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/05/data-src-image-a1864d0e-327f-40f8-a1c2-555ae337cb03.png" class="kg-image" alt="A screenshot of a computer

AI-generated content may be incorrect." loading="lazy" width="363" height="243"></figure> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Inline Lists and Graph Visualizations in Scheduled Platform Analytics Data Visualizations in Yokohama ]]></title>
        <description><![CDATA[ For years, scheduled reports have been the standard solution for automatically exporting and sharing platform data with stakeholders on a regular cadence. One limitation with this solution that I&#39;ve often received complaints about, however, has been its reliance upon a document (PDF or Excel, depending on the type ]]></description>
        <link>https://relay.semaphorepartners.com/articles/inline-lists-and-graph-visualizations-in-scheduled-platform-analytics-data-visualizations-in-yokohama/</link>
        <guid isPermaLink="false">680a5929b3797f00010a4366</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Tom Hughes ]]></dc:creator>
        <pubDate>Fri, 02 May 2025 09:25:19 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/05/pa_visualizations.png" medium="image"/>
        <content:encoded><![CDATA[ <p>For years, scheduled reports have been the standard solution for automatically exporting and sharing platform data with stakeholders on a regular cadence. One limitation with this solution that I've often received complaints about, however, has been its reliance upon a document (PDF or Excel, depending on the type of report) attached to the automated email, with no option to display the content inline. This solution gets the job done, but requires additional clicks to view the relevant content and especially complicates things on mobile mail clients. As with most things in the ServiceNow platform, <a href="https://www.servicenow.com/community/performance-analytics-blog/embed-list-reports-in-a-scheduled-report-email/ba-p/2269771?ref=relay.semaphorepartners.com" rel="noreferrer">creative solutions</a> have provided workarounds that allow for system administrators to better meet stakeholders' user experience expectations. With the Yokohama release, this often-requested feature is available out-of-box for Data Visualizations as part of ServiceNow's Platform Analytics Experience.</p><h2 id="scheduling-inline-content-exports">Scheduling inline content exports</h2><p>While not explicitly called out <a href="https://www.servicenow.com/docs/bundle/yokohama-now-intelligence/page/use/par-for-workspace/task/schedule-visn-export-vd.html?ref=relay.semaphorepartners.com" rel="noreferrer">in the product documentation</a>, the Yokohama release includes a few somewhat hidden options in the scheduled export screen that allow for a bit more flexibility in how scheduled Data Visualization exports are rendered.</p><p>To kick things off, you'll want to navigate to <strong>All &gt; Platform Analytics &gt; Library </strong>and select <strong>Data Visualizations</strong>. It's worth noting here that these same options are not available for Dashboards at this time (and possibly won't be in the future, due to the volume of content these can contain), though the ability to export entire Platform Analytics Dashboards on a schedule as PDF or PowerPoint files is pretty convenient. </p><p>Once you've selected the Data Visualization you'd like to embed in an email, you'll want to open the Schedule screen.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-5.png" class="kg-image" alt="A screenshot of the Platform Analytics Data Visualization editor." loading="lazy" width="1918" height="374" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/04/image-5.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/04/image-5.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2025/04/image-5.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-5.png 1918w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The Schedule option is available under the More Actions menu to the right of the Save button.</span></figcaption></figure><p>From here, you'll want to drill into the <strong>File type</strong> dropdown menu.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-6-1.png" class="kg-image" alt="Screenshot of the Scheduled Export screen in Platform Analytics." loading="lazy" width="1606" height="625" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/04/image-6-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/04/image-6-1.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2025/04/image-6-1.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-6-1.png 1606w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The "Embed" options are a new and welcome addition.</span></figcaption></figure><p>The exact options available are dependent on the type of Data Visualization you're trying to schedule. The screenshot above is from a "Pie" visualization, while "List - Simple" visualizations will include an "Embed LIST" option.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-7.png" class="kg-image" alt="The Embed LIST option in the Scheduled Export screen for a list Data Visualization." loading="lazy" width="1194" height="265" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/04/image-7.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/04/image-7.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-7.png 1194w" sizes="(min-width: 720px) 720px"></figure><p>Using the "Add" button, you're able to include multiple Visualizations to embed in a single scheduled export.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-11.png" class="kg-image" alt="" loading="lazy" width="1179" height="417" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/04/image-11.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/04/image-11.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-11.png 1179w" sizes="(min-width: 720px) 720px"></figure><p>With at least one "Embed" option selected, you're able to modify the exact email contents in the "Email Details" section below. The embedded content is added to the WYSIWYG editor in the order in which it was added.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-14.png" class="kg-image" alt="" loading="lazy" width="1164" height="633" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/04/image-14.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/04/image-14.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-14.png 1164w" sizes="(min-width: 720px) 720px"></figure><p>Once the content is arranged as you'd like and at least one recipient is configured, you can test the export by saving and clicking the "Send Now" button.</p><h2 id="results">Results</h2><p>Immediately upon testing, you should be able to preview the generated email in your system's </p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-15-1.png" class="kg-image" alt="Screenshot of a generated email notification " loading="lazy" width="806" height="872" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/04/image-15-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-15-1.png 806w" sizes="(min-width: 720px) 720px"></figure><h2 id="nuances-and-limitations">Nuances and limitations</h2><p>This is a very exciting new feature, but there are a handful of things to note before diving in fully.</p>
<!--kg-card-begin: html-->
<ul>
  <li>If embedding multiple Visualizations, it can be a bit difficult to differentiate them in the WYSIWYG.</li>
  <li>For "List - Simple" visualizations, a raw HTML table is always output including links to all reference records in the Core UI. Workspace links are not supported, and Group By options do not result in multiple tables being generated. The styling of the table and the text that appears below it linking to the Visualization in-platform both seem to be hard-coded, which limits flexibility and potentially the audience of these notifications to users with Platform Analytics access.</li>
  <li>From what I can see, there does not appear to be any support for any kind of company branding in the generated exports using any existing features like Email Templates or Layouts.</li>
</ul>
<!--kg-card-end: html-->
<p>Despite these points, this feature requires no custom code and empowers reporting power users to create friendlier experiences for a variety of personas.</p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Adding Variables into List Reports on Platform Analytics Dashboards in ServiceNow - Workaround! - CS7468024 ]]></title>
        <description><![CDATA[ As ServiceNow continues to evolve its reporting capabilities, the new Platform Analytics Dashboards offer powerful ways to visualize and analyze your instance data. While these dashboards bring numerous benefits to the table, there are still a few quirks that administrators and developers should be aware of.



The Promise of Platform ]]></description>
        <link>https://relay.semaphorepartners.com/articles/adding-variables-into-list-reports-on-platform-analytics-dashboards-in-servicenow-workaround-cs7468024/</link>
        <guid isPermaLink="false">67ed62d395e5450001b41aa2</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Thu, 10 Apr 2025 08:42:26 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/platform-analytics.png" medium="image"/>
        <content:encoded><![CDATA[ <p>As ServiceNow continues to evolve its reporting capabilities, the new Platform Analytics Dashboards offer powerful ways to visualize and analyze your instance data. While these dashboards bring numerous benefits to the table, there are still a few quirks that administrators and developers should be aware of.<br></p><h2 id="the-promise-of-platform-analytics">The Promise of Platform Analytics</h2><p>The new Platform Analytics Dashboards represent a significant step forward in ServiceNow reporting capabilities. They offer:</p><ul><li>Improved performance for handling large datasets</li><li>More interactive and dynamic visualizations</li><li>Better integration with other ServiceNow components</li><li>Enhanced customization options for different user needs</li></ul><h2 id="the-challenge-with-adding-variables">The Challenge with Adding Variables</h2><p>Despite these advantages, there's one particular feature that doesn't quite work as expected yet: <strong>adding variables to a list view (List - Simple) report</strong>.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-3.png" class="kg-image" alt="" loading="lazy" width="590" height="1382"></figure><p>Here's what happens:</p><ol><li>You add a variable as a column to your list view report</li><li>The column appears in the sidebar</li><li>You refresh the report, but the column doesn't even show up</li></ol><p>This issue has been documented <a href="https://www.servicenow.com/community/platform-analytics-forum/visualization-question-variable-in-a-simple-list/m-p/3082973?ref=relay.semaphorepartners.com" rel="noreferrer">here (8 months ago)</a>, and referenced a ServiceNow Support Case (CS7468024) which I'm unable to view. </p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-4.png" class="kg-image" alt="" loading="lazy" width="1164" height="176" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/04/image-4.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/04/image-4.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-4.png 1164w" sizes="(min-width: 720px) 720px"></figure><h2 id="the-workaround-manual-intervention">The Workaround: Manual Intervention</h2><p>Not being able to take no as an answer, I was able to find this as a workaround. </p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image.png" class="kg-image" alt="" loading="lazy" width="658" height="378" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/04/image.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image.png 658w"></figure><p></p><ol><li>Click Open record on the specific visualization you want to include a variable on</li><li>Copy and paste the <code>json</code> value into the text editor of your choice, and then format it</li></ol><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-2.png" class="kg-image" alt="" loading="lazy" width="1946" height="188" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/04/image-2.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/04/image-2.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2025/04/image-2.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/image-2.png 1946w" sizes="(min-width: 720px) 720px"></figure><ol start="3"><li>You'll see that the variable columns are in this format</li></ol><p><code>variables.{catalog item sys_id}.{variable sys_id}</code></p><ol start="4"><li>For each variable column, remove the Catalog Item's sys_id, so instead it's just </li></ol><p><code>variables.{variable sys_id}</code></p><ol start="5"><li>Paste the json back in, save the record and refresh the Platform Analytics Dashboard and confirm that the variable columns now show up</li></ol><h2 id="conclusion">Conclusion</h2><p>Since we've been able to do this in typical reporting for quite some time, it's frustrating that ServiceNow doesn't have a fix for this yet. They tend to release (and market) new features that haven't reached parity yet with existing functionality. However, you should be able to use this workaround until they issue a fix for it. </p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ ServiceNow: Beautiful JSON in the Activity Log ]]></title>
        <description><![CDATA[ Have you ever needed to display a JSON object in a ServiceNow comment or work note? Maybe an integration has failed and we need to convey failure information to a technician.

Easy peasy, let&#39;s just do something like current.work_notes = JSON.stringify(myData); right?



Our Example JSON ]]></description>
        <link>https://relay.semaphorepartners.com/articles/servicenow-beautiful-json-in-the-activity-log/</link>
        <guid isPermaLink="false">67ed8d62c1d3d20001d91ec2</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Billy Matthews ]]></dc:creator>
        <pubDate>Fri, 04 Apr 2025 11:07:51 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/Beautiful-JSON.png" medium="image"/>
        <content:encoded><![CDATA[ <p>Have you ever needed to display a JSON object in a ServiceNow comment or work note? Maybe an integration has failed and we need to convey failure information to a technician. </p><p>Easy peasy, let's just do something like <code>current.work_notes = JSON.stringify(myData);</code> right?</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/CleanShot-2025-04-02-at-14.16.11@2x.png" class="kg-image" alt="" loading="lazy" width="2000" height="292" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/04/CleanShot-2025-04-02-at-14.16.11@2x.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/04/CleanShot-2025-04-02-at-14.16.11@2x.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2025/04/CleanShot-2025-04-02-at-14.16.11@2x.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/CleanShot-2025-04-02-at-14.16.11@2x.png 2026w" sizes="(min-width: 1200px) 1200px"></figure><div class="kg-card kg-toggle-card" data-kg-toggle-state="close">
            <div class="kg-toggle-heading">
                <h4 class="kg-toggle-heading-text"><span style="white-space: pre-wrap;">Our Example JSON Object</span></h4>
                <button class="kg-toggle-card-icon" aria-label="Expand toggle to read content">
                    <svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                        <path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"></path>
                    </svg>
                </button>
            </div>
            <div class="kg-toggle-content"><p><span style="white-space: pre-wrap;">{"id":"card-123","title":"Getting Started with ServiceNow","description":"Learn the basics of ServiceNow platform and how to create your first application","category":"education","tags":["servicenow","beginner","tutorial"],"metadata":{"created":"2025-03-15T10:30:00Z","updated":"2025-03-28T14:45:22Z","author":{"id":"user-456","name":"Jane </span><a href=""><span style="white-space: pre-wrap;">Developer","email":"jane.dev@example.com</span></a><span style="white-space: pre-wrap;">"}},"stats":{"views":1245,"likes":89,"shares":32},"isPublished":true,"thumbnailUrl":"</span><a href="https://assets.example.com/images/servicenow-101.jpg%22,%22linkUrl%22:%22/courses/servicenow-basics?ref=relay.semaphorepartners.com"><span style="white-space: pre-wrap;">https://assets.example.com/images/servicenow-101.jpg","linkUrl":"/courses/servicenow-basics</span></a><span style="white-space: pre-wrap;">"}</span></p></div>
        </div><p>Uhh, okay. It's there but it doesn't exactly look great and the readability is abysmal. </p><p>Let's do better. We can use the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify?ref=relay.semaphorepartners.com" rel="noreferrer"><code>space</code></a> parameter to improve our formatting: <code>current.work_notes = JSON.stringify(myData, null, 2);</code>.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/CleanShot-2025-04-02-at-14.37.20@2x.png" class="kg-image" alt="" loading="lazy" width="2000" height="292" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/04/CleanShot-2025-04-02-at-14.37.20@2x.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/04/CleanShot-2025-04-02-at-14.37.20@2x.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2025/04/CleanShot-2025-04-02-at-14.37.20@2x.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/CleanShot-2025-04-02-at-14.37.20@2x.png 2026w" sizes="(min-width: 1200px) 1200px"></figure><p>Well that didn't really work (but it does in <code>gs.log</code>, try it out!). Now we've got these <code>\n</code> 's everywhere and our spaces seem to have been swallowed by the ether. </p><p>Let's leverage HTML, that way we have more control: </p><p><code>current.work_notes = '[code]&lt;pre&gt;&lt;code&gt;' + JSON.stringify(myData, null, 2).replaceAll('\n', '&lt;br /&gt;') + '&lt;/code&gt;&lt;/pre&gt;[/code]';</code></p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/CleanShot-2025-04-02-at-14.49.02@2x.png" class="kg-image" alt="" loading="lazy" width="2000" height="1144" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/04/CleanShot-2025-04-02-at-14.49.02@2x.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/04/CleanShot-2025-04-02-at-14.49.02@2x.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2025/04/CleanShot-2025-04-02-at-14.49.02@2x.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/CleanShot-2025-04-02-at-14.49.02@2x.png 2014w" sizes="(min-width: 1200px) 1200px"></figure><p>And we're there! In this final example, we use the <a href="https://www.servicenow.com/docs/bundle/yokohama-platform-administration/page/administer/field-administration/task/render-journal-field-entries-as-html.html?ref=relay.semaphorepartners.com" rel="noreferrer"><code>[code]</code></a> tag to tell ServiceNow we intend to render HTML in this work note, then we use the <code>&lt;pre&gt;</code> tag in HTML to preserve white space, then the <code>&lt;code&gt;</code> tag to semantically inform the browser (and screen readers) that the enclosed is code (visually this tag isn't strictly necessary as omitting it in this context will render the same output), and finally we use <code>.replaceAll('\n', '&lt;br /&gt;')</code> to convert our newlines to a format that HTML understands. </p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Revolutionizing Work Management at Lumon Industries with ServiceNow ]]></title>
        <description><![CDATA[ When Lumon Industries approached Semaphore Partners about implementing ServiceNow, we knew we were in for a unique challenge. The company&#39;s innovative workplace management system and distinct departmental structure required a sophisticated approach to service management and workflow automation.


The Challenge

Lumon Industries, known for its groundbreaking work in ]]></description>
        <link>https://relay.semaphorepartners.com/articles/revolutionizing-work-management-at-lumon-industries-with-servicenow/</link>
        <guid isPermaLink="false">67ae4aea8b3cd2000144832f</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Tue, 18 Feb 2025 15:17:27 -0500</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/02/lumon.jpeg" medium="image"/>
        <content:encoded><![CDATA[ <p>When Lumon Industries approached Semaphore Partners about implementing ServiceNow, we knew we were in for a unique challenge. The company's innovative workplace management system and distinct departmental structure required a sophisticated approach to service management and workflow automation.</p><h2 id="the-challenge">The Challenge</h2><p>Lumon Industries, known for its groundbreaking work in data refinement, operates with a distinctive organizational structure where departments are strictly segregated. Their "severed" employees work in completely isolated environments, necessitating specialized workflow management solutions that could maintain strict information boundaries while enabling efficient operations.</p><h2 id="the-solution">The Solution</h2><p>Working closely with Lumon's management team, Semaphore Partners developed a precision-engineered ServiceNow implementation that respected their unique operational requirements while modernizing their service management capabilities.</p><h3 id="advanced-security-framework">Advanced Security Framework</h3><p>We architected a sophisticated security framework within ServiceNow that mirrors Lumon's strict departmental segregation:</p><ul><li>Role-based access control (ACLs) with granular permissions based on employee status</li><li>Automated workflow state transitions (playbooks &amp; Flow) between severed and non-severed environments</li><li>Secure data partitioning (ServiceNow data separation) to maintain complete separation between departments</li></ul><h3 id="technical-implementation-highlights">Technical Implementation Highlights</h3><p>Our implementation leveraged several key ServiceNow capabilities:</p><ul><li><strong>Service Portal</strong>: Purposefully designed employee portals featuring the signature Lumon green-on-black terminal interface, complete with specialized numeric grid displays for data refinement tasks</li><li><strong>Flow Designer</strong>: Automated workflows tracking completion percentages across files like "Le Mars" and managing the sophisticated five-bin refinement sorting system</li><li><strong>Performance Analytics</strong>: Real-time monitoring dashboards showing refinement progress (0% to 100%) with dedicated metrics for each data quadrant (00-04)</li><li><strong>CMDB</strong>: Comprehensive tracking of refinement terminals, featuring the proprietary Lumon interface with its distinctive number matrix display</li><li><strong>Now Intelligence</strong>: AI-powered pattern recognition for identifying scary, malicious, and tumorous numbers within data sets</li><li><strong>Virtual Agent</strong>: Automated compliance monitoring for proper number categorization procedures</li></ul><h2 id="the-results">The Results</h2><p>The implementation of ServiceNow has transformed Lumon's service management capabilities while maintaining their strict operational requirements:</p><ul><li>40% reduction in workflow transition times between departments</li><li>60% improvement in resource allocation efficiency</li><li>Enhanced security compliance through automated controls</li><li>Streamlined incident response times for critical systems</li></ul><blockquote>"Semaphore Partners' understanding of our unique operational requirements and their ability to adapt ServiceNow to meet these needs has been invaluable. The platform has become an integral part of our daily operations while maintaining our strict departmental segregation protocols." - Seth Milchick, Supervisor of the Severed Floor, Lumon Industries</blockquote><p><em>Want to learn how Semaphore Partners can help your organization leverage ServiceNow to its full potential? Contact us today for a consultation.</em></p><hr><p><em>Note: This case study is a work of fiction based on the Apple TV+ series "Severance." While the ServiceNow implementation details are grounded in real platform capabilities, the client company and specific use cases are fictional. The post is intended to demonstrate creative possibilities in enterprise service management through a pop culture lens.</em></p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Streamlining Employee Onboarding: Integrating ServiceNow with Greenhouse Recruiting ]]></title>
        <description><![CDATA[ Integrating ServiceNow with Greenhouse streamlined tracking new hires, replacing error-prone spreadsheets. By leveraging the API, we eliminated duplicate entries, automated onboarding tasks, and delivered detailed reports, empowering teams to focus on welcoming new employees efficiently. ]]></description>
        <link>https://relay.semaphorepartners.com/articles/streamlining-employee-onboarding-integrating-servicenow-with-greenhouse-recruiting/</link>
        <guid isPermaLink="false">678ac4200f5f9c000183e55d</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Tue, 21 Jan 2025 10:23:18 -0500</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/01/greenhouse.png" medium="image"/>
        <content:encoded><![CDATA[ <p>In today’s competitive job market, delivering a seamless onboarding experience is a crucial factor for employee retention and satisfaction. As a ServiceNow partner and implementation specialist, we recently tackled the challenge of integrating the onboarding process from Greenhouse, a leading recruiting platform with ServiceNow. The goal? To ensure that internal teams have a streamlined way to track new hires and manage their onboarding process effectively.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/01/632a6560c52cbd0f904e4561_Greenhouse-wordmark-recruiting-1.svg" class="kg-image" alt="" loading="lazy" width="588" height="67"></figure><h2 id="starting-simple-an-email-based-integration">Starting Simple: An Email-Based Integration</h2><p>When we first approached this project, we opted for a quick-win solution: using Greenhouse’s email notifications as the ingestion point for onboarding data. Every time a candidate accepted an offer, Greenhouse sent an email containing basic information about the candidate and their job. Our integration parsed these emails and used the extracted data to trigger onboarding workflows in ServiceNow.</p><p>While this approach helped us go live quickly, it wasn’t without its challenges:</p><ol><li><strong>Email Format Variability</strong>: Greenhouse’s email templates occasionally changed, causing our parsing logic to fail.</li><li><strong>Limited Data Access</strong>: The emails didn’t include all the necessary details, such as job-specific onboarding requirements or candidate preferences, which meant that manual intervention was still required.</li><li><strong>Duplicate Issues</strong>: Multiple emails for the same candidate would often trigger duplicate new hire entries, leading to confusion and errors in the onboarding process.</li><li><strong>Not a Real Integration: </strong>This approach relies on infrastructure outside of the ServiceNow instance, since it's waiting for an email to be sent and then received. It also relies on asynchronous event handling in ServiceNow, which could be overloaded for other reasons.</li><li><strong>Data-enforcement: </strong>Since an email is just a big text field, no data can be enforced (or rejected).</li></ol><p>Ultimately, we realized that email-based integration was not a sustainable or scalable solution. We needed a more robust approach that leveraged Greenhouse’s API.</p><h2 id="building-a-robust-api-based-integration">Building a Robust API-Based Integration</h2><p>To create a seamless integration, we transitioned to Greenhouse’s API. This allowed us to query detailed and consistent data directly from Greenhouse and eliminate reliance on email.</p><h3 id="api-overview">API Overview</h3><p>Greenhouse's API is well documented here: <a href="https://developer.greenhouse.io/harvest.html?ref=relay.semaphorepartners.com#introduction">https://developer.greenhouse.io/harvest.html#introduction</a></p><p>The integration requires an API Token, and authorizes via basic auth using the API token as the username with a blank password. The Greenhouse Dev Center also allows you to specify which endpoints are accessible for which API token. Best practices are to only allow access to the endpoints that are needed. </p><h3 id="querying-the-offers-endpoint">Querying the Offers Endpoint</h3><p>Since the process begins once an offer is accepted, the integration begins by querying the&nbsp;<code>GET /offers</code>&nbsp;endpoint periodically from Greenhouse’s API. This endpoint provides information about:</p><ul><li>Candidate ID</li><li>Job ID</li><li>Offer details, including the accepted status and start date</li></ul><p>While the Offers API gives us a good starting point, the data it returns is incomplete for our onboarding purposes. For example, the offer information might include the candidate’s ID and job ID, but not their name, email, or job title—critical details for provisioning accounts, equipment, and training.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/01/image-1.png" class="kg-image" alt="" loading="lazy" width="2000" height="1343" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/01/image-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/01/image-1.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2025/01/image-1.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w2400/2025/01/image-1.png 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Screenshot of ServiceNow REST Message Function to retrieve details for multiple accepted offers updated after a certain date/time</span></figcaption></figure><h3 id="supplementing-data-with-additional-endpoints">Supplementing Data with Additional Endpoints</h3><p>To fill in the gaps, our integration makes additional calls to:</p><ol><li><strong>Candidate Endpoint (<code>GET /candidates/{id}</code>):</strong></li></ol><ul><li>Retrieves the candidate’s full name and email</li></ul><ol start="2"><li><strong>Job Endpoint (<code>GET /jobs/{id}</code>):</strong></li></ol><ul><li>Provides job title, department, location, and other custom fields relevant to our onboarding workflows</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/01/image-2.png" class="kg-image" alt="" loading="lazy" width="2000" height="1306" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/01/image-2.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/01/image-2.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2025/01/image-2.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w2400/2025/01/image-2.png 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Screenshot of ServiceNow REST Message Function to retrieve details for multiple candidates</span></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/01/image-3.png" class="kg-image" alt="" loading="lazy" width="2000" height="428" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2025/01/image-3.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2025/01/image-3.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2025/01/image-3.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w2400/2025/01/image-3.png 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Screenshot of ServiceNow REST Message Function to retrieve details for a single job</span></figcaption></figure><p>By combining data from these three endpoints, we create a comprehensive onboarding profile for each new hire. This profile includes everything ServiceNow needs to automate onboarding tasks, from creating IT tickets for laptop provisioning to scheduling orientation sessions.</p><h3 id="data-source-details">Data Source Details</h3><p>Our Data Source loads the data by script, since we have to make these three calls to efficiently map and transform all of this data at once. </p><p>Our data source only look at Offers that are</p><ul><ul><li>Status = Accepted (see screenshot above)</li><li>Updated after last 14 days + 2 hours</li><li>Starting After today</li></ul></ul><p>This also means that our Scheduled Import Set should run at an interval less that 14 days, to ensure we don't miss any accepted offers. </p><p>As detailed above, we can get all Offer details with one call, and get all Candidate details with only one call, but unfortunately, the Jobs endpoint doesn't allow for bulk queries. This means that if we have <em>n</em> new offers, we have to make <em>n</em>+ 2 REST Message calls. </p><pre><code class="language-javascript">(function loadData(import_set_table, data_source, import_log, last_success_import_time) {

    var gdt = new GlideDateTime();
    var startsAfter = gdt.getDate();
    gdt.addDaysLocalTime(-14);
    gdt.addSeconds(-2 * 60 * 60);
    var updated_after = gdt.toString().replace(" ", "T") + ".000Z";

    var message = new sn_ws.RESTMessageV2('Greenhouse', 'offers-recent');

    message.setStringParameter("starts_after", startsAfter);
    message.setStringParameter("updated_after", updated_after);

    var arr = []


    var resultStream = global.ukg_streamData(message);
    var offerStream = resultStream.map(function(x) {
        return {
            "candidate_id": x["candidate_id"].toString(),
            "starts_at": x["starts_at"],
            "opening_id": x["opening"]["opening_id"],
            "job_id": x["job_id"].toString()
        }
    }).chunk(50).flatMap(function(offers) {
        var offersMap = offers.reduce(function(map, item) {
            map[item.candidate_id] = item;
            return map;
        }, {});

        var candidateIDs = Object.keys(offersMap);
        
		var jobIDs = offers.map(function(x) {
            return x.job_id;
        })

        offers.forEach(function(x){
            var message = new sn_ws.RESTMessageV2('Greenhouse', 'job');
            message.setStringParameter("id", x.job_id.toString());
            var response = message.execute();        
            var responseBody = response.getBody();
            var httpStatus = response.getStatusCode();

            if(httpStatus == 200){
                var jobResponse = JSON.parse(responseBody);
                x["remote_office_location"] = jobResponse["custom_fields"]["remote_office_location"];
                x["jobtitle"] = jobResponse["name"];
            }
        })

        var message = new sn_ws.RESTMessageV2('Greenhouse', 'candidates_by_ids');
        message.setStringParameter("candidate_ids", candidateIDs.toString());
        var candidateStream = global.ukg_streamData(message);

        return candidateStream.map(function(candidate) {
            var offerData = offersMap[candidate.id];
            var personalEmail = candidate.email_addresses.find(function(email) {
                return email.type === "personal";
            });
          
            return global.GQ.merge(offerData, {
                "name": candidate.first_name + " " + candidate.last_name,
                "first_name": candidate.first_name,
                "last_name": candidate.last_name,
                "preferred_name": candidate.custom_fields["preferred_name"] || "",                
                "personal_email": personalEmail &amp;&amp; personalEmail.value,
                "end_date": candidate.custom_fields["end_date"] || "",
                "location": candidate.custom_fields["office_location"] || ""
            });
        });
    });

    offerStream.forEach(function(result) {
        import_set_table.insert(result);
    });

})(import_set_table, data_source, import_log, last_success_import_time);</code></pre><h2 id="automating-processes-to-assist-internal-teams">Automating Processes to Assist Internal Teams</h2><p>The integration wasn’t just about gathering data—it was about making life easier for the internal team managing onboarding. In addition to streamlining data tracking, we implemented the following enhancements:</p><ol><li><strong>Automated Email Reports</strong>: Weekly emailed reports detail upcoming new hires, providing the team with a clear overview of who’s starting and when.</li><li><strong>Automatic Case Creation</strong>: Cases are automatically created in ServiceNow two weeks and one week before each new hire’s start date. These cases ensure that key tasks—like equipment setup and access provisioning—are completed on time.</li></ol><h2 id="benefits-of-the-api-based-approach">Benefits of the API-Based Approach</h2><p>Switching to an API-based integration has delivered significant benefits:</p><ol><li><strong>Data Consistency</strong>: By querying Greenhouse directly, we ensure that onboarding workflows always have the most up-to-date information.</li><li><strong>Scalability</strong>: The API handles varying volumes of data seamlessly, enabling us to scale as hiring increases.</li><li><strong>Improved Team Efficiency</strong>: Automated reports and cases reduce manual tracking and ensure nothing falls through the cracks.</li></ol><h2 id="lessons-learned">Lessons Learned</h2><p>Looking back, the shift from an email-based integration to an API-based solution has been transformative. However, this evolution taught us a few key lessons:</p><ul><li><strong>Start Simple, But Plan for Growth</strong>: Email-based integrations can help you go live quickly, but they’re rarely sustainable for high-volume processes. Be ready to iterate.</li><li><strong>Understand the Data Model</strong>: Before building an API integration, familiarize yourself with the platform’s endpoints and data relationships to ensure you’re pulling all necessary information.</li><li><strong>Test for Edge Cases</strong>: Not all candidates or jobs are the same. Test your integration with a variety of scenarios to identify gaps in data or logic.</li></ul><h2 id="conclusion">Conclusion</h2><p>Integrating ServiceNow with Greenhouse Recruiting has not only streamlined our clients’ onboarding processes but also empowered internal teams to manage new hires effectively. By investing in a robust integration, we’ve eliminated manual tracking, automated critical tasks, and ensured a consistent, scalable process for future growth.</p><p>If your organization is looking to enhance its onboarding capabilities, we’d love to share more about how we can help. Reach out to us today to start the conversation!</p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Workspace: Field Decorators open a URL ]]></title>
        <description><![CDATA[ Creating a way to deep link to an external system is a great for avoiding lengthy &amp; unnecessary integrations. Providing agents a way to get directly into a system without multiple clicks can be a huge time saver.

Prior to Workspace, adding a Field Decorator was accomplished by creating a ]]></description>
        <link>https://relay.semaphorepartners.com/articles/workspace-field-decorators-open-a-url/</link>
        <guid isPermaLink="false">673ca5751852110001581fd3</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Wed, 08 Jan 2025 14:17:38 -0500</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/01/workspace--field-decorators-open-url.jpeg" medium="image"/>
        <content:encoded><![CDATA[ <p>Creating a way to deep link to an external system is a great for avoiding lengthy &amp; unnecessary integrations. Providing agents a way to get directly into a system without multiple clicks can be a huge time saver. </p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/11/Field-Decorator-2.png" class="kg-image" alt="" loading="lazy" width="1450" height="1186" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/11/Field-Decorator-2.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/11/Field-Decorator-2.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/11/Field-Decorator-2.png 1450w" sizes="(min-width: 720px) 720px"></figure><p>Prior to Workspace, adding a Field Decorator was accomplished by creating a UI Macro. To implement a UI macro a few steps were needed to getting your decorator on a form, including writing code.</p><p>Similarly, if you are using one of the Baseline Workspaces (SOW, ITAM, CSM/FSM, HR) or a custom workspace there are some steps to create a Field Decorator. Luckily no Jelly, HTML, or JS is involved. There is mostly no code, there is a bit of JSON to copy/paste, and the rest is all configuration driven. </p><p>These steps seems pretty simple/straightforward, but depending on your workspace configuration there may be a few additional steps to make your Field Decorator launch an external URL. </p><h2 id="creating-the-field-decorator">Creating the Field Decorator</h2><ol><li>Navigate to <strong>Now Experience Framework &gt; Declarative Actions &gt; Field Decorators</strong></li><li>Fill in the following fields:<ol><li><strong>Action label:</strong> <em>Provide a descriptive label</em></li><li><strong>Implemented as</strong>: <u>UXF Client Action</u></li><li><strong>Specify client action:</strong> <u>Open URL</u></li><li><strong>Decorator applies to: </strong><u>Specific field</u></li><li><strong>Field Name:</strong> <em>Select a field name to display the icon near</em></li><li><strong>Icon:</strong> <em>Select an icon</em></li><li><strong>Table:</strong> S<em>elect table</em></li></ol></li><li><strong>Save</strong> the form</li><li>Click Related Links &gt; <strong>Advanced view</strong></li><li>Set URL:<ol><li><strong>Action Attributes &gt; Payload Map &gt; URL</strong></li><li>Fill in your <strong>URL</strong></li><li>use <code>{{actionModelField}}</code>syntax for example <em><code>notion.so/{{displayValue}}</code></em></li></ol></li><li><strong>Save</strong> the Form</li><li><strong>Navigate</strong> to the <strong>Workspace</strong>:<ol><li>Select a Record from the table you configured on the Action Assignment</li><li>Confirm your Icon appears next to your field</li></ol></li></ol><p>At this point if you click your field decorator icon, nothing will happen. One additional (<em><u>undocumented</u></em>) set of steps is required to make the decorator actually launch/open a URL.</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">💡</div><div class="kg-callout-text">Check the Related List - "Action Model Fields" for other variables to use within the Payload Map url</div></div><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/11/CleanShot-2024-11-19-at-10.41.00@2x.png" class="kg-image" alt="" loading="lazy" width="2000" height="1586" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/11/CleanShot-2024-11-19-at-10.41.00@2x.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/11/CleanShot-2024-11-19-at-10.41.00@2x.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2024/11/CleanShot-2024-11-19-at-10.41.00@2x.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/11/CleanShot-2024-11-19-at-10.41.00@2x.png 2156w" sizes="(min-width: 720px) 720px"></figure><p></p><h2 id="creating-the-ux-add-on-event-mapping">Creating the UX Add-on Event Mapping</h2><ol><li>Navigate to the Action Assignment (Decorator) you created<ol><li>Related List &gt; UX Add-on Event Mapping</li></ol></li><li>Create a new UX Add-on Event Mapping<ol><li>Fill in the following fields:<ol><li><strong>Event Mapping Name</strong>: <em>Provide a unique name</em></li><li><strong>Source Component: </strong><em>Form</em></li><li><strong>Source Declarative Action:</strong> <em>Action Assignment from previous steps</em></li><li><strong>Controller: </strong><em>UI Controller Record Page</em></li><li><strong>Target Event: </strong><em>[Record Page] Item Selected</em></li><li><strong>Target Payload Mapping: </strong><em>Use payload below:</em></li></ol></li></ol></li></ol><pre><code>{
    "type": "MAP_CONTAINER",
    "container": {
        "route": {
            "type": "JSON_LITERAL",
            "value": "external"
        },
        "fields": {
            "type": "JSON_LITERAL",
            "value": null
        },
        "params": {
            "type": "JSON_LITERAL",
            "value": null
        },
        "redirect": {
            "type": "JSON_LITERAL",
            "value": null
        },
        "passiveNavigation": {
            "type": "JSON_LITERAL",
            "value": null
        },
        "title": {
            "type": "JSON_LITERAL",
            "value": null
        },
        "multiInstField": {
            "type": "JSON_LITERAL",
            "value": null
        },
        "external": {
            "type": "MAP_CONTAINER",
            "container": {
                "url": {
                    "type": "EVENT_PAYLOAD_BINDING",
                    "binding": {
                        "address": [
                            "url"
                        ]
                    }
                }
            }
        }
    }
}</code></pre> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Integrating UKG With ServiceNow to Supplement Organizational Data ]]></title>
        <description><![CDATA[ Introduction

Many organizations leverage Active Directory for good reason—it serves as an excellent central repository for network resources like users, groups, and computers. This data can be imported into ServiceNow, providing visibility into both users as well as Active Directory groups and group memberships.

However, what about valuable data ]]></description>
        <link>https://relay.semaphorepartners.com/articles/integrating-ukg-with-servicenow-to-supplement-organizational-data/</link>
        <guid isPermaLink="false">6759b8c35e245b00019d6afe</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Thu, 19 Dec 2024 12:13:49 -0500</pubDate>
        <media:content url="" medium="image"/>
        <content:encoded><![CDATA[ <p></p><h2 id="introduction"><strong>Introduction</strong></h2><p>Many organizations leverage Active Directory for good reason—it serves as an excellent central repository for network resources like users, groups, and computers. This data can be imported into ServiceNow, providing visibility into both users as well as Active Directory groups and group memberships.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/12/image.png" class="kg-image" alt="" loading="lazy" width="698" height="396" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/12/image.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/12/image.png 698w"></figure><p>However, what about valuable data that exists outside Active Directory, such as UKG Human Resource information? UKG stores essential data about users, locations, organizations, and companies, offering deep insights into your organization and its people.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/12/image-1.png" class="kg-image" alt="" loading="lazy" width="1126" height="410" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/12/image-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/12/image-1.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/12/image-1.png 1126w" sizes="(min-width: 720px) 720px"></figure><p>Cross-referencing data across multiple sources of truth can be difficult, tedious, and time-consuming, but allows for a more complete picture of your organization within ServiceNow.</p><h3 id="we-can-leverage-servicenow-to-integrate-with-your-sources-of-truth"><strong>We can leverage ServiceNow to integrate with your sources of truth.</strong></h3><p>In ServiceNow, we import data from additional sources (in this case, UKG Human Resources) and combine it with existing Active Directory data via Transform Maps to create a single "3-Dimensional" data point. This connects people, places, and things to provide comprehensive insights into your organization.</p><p>A great example of this is Location data. A user’s location in Active Directory may just be a string “New Jersey”, but you will need more details. What’s the location’s city, address or zip code? Supplementing this referential data can help with answering ‘how many users are located in each time zone’? Or who is the department head to approve a certain catalog request?</p><h2 id="data-sources-rest-messages"><strong>Data Sources / REST Messages</strong></h2><p>Data Sources / REST Messages act as "getters" of data, enabling ServiceNow to communicate with external sources like UKG Human Resources. You can request various data points such as user start dates, employment status, and location records using REST Messages.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/12/image-2.png" class="kg-image" alt="" loading="lazy" width="754" height="436" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/12/image-2.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/12/image-2.png 754w" sizes="(min-width: 720px) 720px"></figure><p>Data requests are secure—authentication is required for accessing sensitive data. With UKG Human Resources, you must provide a username and password with your request to validate access, but also provide a Customer API Key and a User API Key.</p><p>Data sources will also allow you to schedule regular data imports to ensure the freshness of the supplemental data.</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">(function loadData(import_set_table, data_source, import_log, last_success_import_time) {

    var message = new sn_ws.RESTMessageV2('UKG integration', 'employee-employment-details');
    var resultStream = ukg_streamData(message);
    resultStream.forEach(function(result) {
        import_set_table.insert({
            "companyID": result["companyID"],
            "employeeID": result["employeeID"],
            "homeCompany": result["homeCompany"],
            "isHomeCompany": result["isHomeCompany"],
            "primaryJobCode": result["primaryJobCode"],
            "primaryWorkLocationCode": result["primaryWorkLocationCode"],
            "jobTitle": result["jobTitle"],
            "fullTimeOrPartTimeCode": result["fullTimeOrPartTimeCode"],
            "originalHireDate": result["originalHireDate"],
            "employeeTypeCode": result["employeeTypeCode"],
            "supervisorID": result["supervisorID"],
            "employeeStatusCode": result["employeeStatusCode"],
            "employeeNumber": result["employeeNumber"]
        });
    });

})(import_set_table, data_source, import_log, last_success_import_time);</code></pre><figcaption><p><span style="white-space: pre-wrap;">An example of a Scripted Data Source to pull in user employment information from UKG via a Stream</span></p></figcaption></figure><h2 id="transform-maps"><strong>Transform Maps</strong></h2><p>Transform maps are the "manipulators" of data. They take the data retrieved by Data Sources / REST Messages and organize it properly within ServiceNow by either creating new records or updating existing ones. For ServiceNow to correctly place data, it needs to coalesce data via a unique identifier that's consistent across all data sources. For user data from both UKG and Active Directory, we used the employee ID. After transforming your data, you have a robust, up-to-date "3-Dimensional" data point.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/12/image-3.png" class="kg-image" alt="" loading="lazy" width="2000" height="804" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/12/image-3.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/12/image-3.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2024/12/image-3.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/12/image-3.png 2048w" sizes="(min-width: 720px) 720px"></figure><p>Have questions, or want to recreate the same record visibility within your organization? Feel free to reach out!</p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Orphan Companies with strange characters being created from SCCM Service Graph Connector (Mojibake) ]]></title>
        <description><![CDATA[ Overview

A client was experiencing a strange issue. Dozens and dozens of companies were being created with bogus Chinese, Japanese and Burmese characters following an otherwise normal name. To make matters worse, these companies didn’t have any associated records with them. Was it foreign involvement? Fortunately, not.


What do ]]></description>
        <link>https://relay.semaphorepartners.com/articles/orphan-companies-with-strange-characters-being-created-from-sccm-service-graph-connector-mojibake/</link>
        <guid isPermaLink="false">675b4d975e245b00019d6b21</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Thu, 12 Dec 2024 15:59:03 -0500</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/Mojibake.png" medium="image"/>
        <content:encoded><![CDATA[ <p></p><h2 id="overview"><strong>Overview</strong></h2><p>A client was experiencing a strange issue. Dozens and dozens of companies were being created with bogus Chinese, Japanese and Burmese characters following an otherwise normal name. To make matters worse, these companies didn’t have any associated records with them. Was it foreign involvement? Fortunately, not.</p><h2 id="what-do-the-characters-mean"><strong>What do the characters mean?</strong></h2><p>Apparently, it’s called Mojibake. I can try to paraphrase, but anything I’ll write would just be regurgitated from wikipedia anyways: <a href="https://en.wikipedia.org/wiki/Mojibake?ref=relay.semaphorepartners.com">https://en.wikipedia.org/wiki/Mojibake</a></p><p>The characters don’t mean anything in succession, but some of my favorite translations were “VMWare Trample Righteous Dazzle” or “Intel Corporation Ken Decorate Hog” (Thanks to Google Translate), Some other examples are pasted below.</p><h2 id="how-did-we-track-it-down"><strong>How did we track it down?</strong></h2><p>Looking at the times these records were created, it was clear that they were from SCCM. Then by looking at the sys_import_set_run table, we could track it down to the Software import. Then through the Integration Studio, we could find out the Transform Script it was using to cleanse the company.</p><h2 id="why-were-they-orphaned"><strong>Why were they orphaned?</strong></h2><p>Thanks in part to ServiceNow’s processing logic, the company is created before it is used. Yes, companies are created before the row is even transformed, thanks to this line:</p><pre><code class="language-javascript">company = new CmdbIntegrationCompanyModelUtil().cleanseCompany(company);
</code></pre><p>Even if the row is rejected, or the Manufacturer is changed, the company is already created! Not good!</p><h2 id="how-did-we-fix-it"><strong>How did we fix it?</strong></h2><p>We could have easily added a line in <strong><em>CmdbIntegrationCompanyModelUtil,</em></strong> but that’s a <strong>high priority</strong>&nbsp;file type. Even the script include that calls that one is a <strong>high priority</strong>&nbsp;file type.</p><p>So in order to prevent skipped records on upgrades, the best thing to do is to create a new script include that calls those, and update the “Cleanse Software Model” sys_rte_eb_operation_type record to call that new custom Script Include.</p><p>For this major problem, the fix was quite simple. ' The one line of code we added was:</p><pre><code class="language-javascript">text = text.replace(/[^\\x00-\\x7F].*/, ""):
</code></pre><p></p><ul><li>terate GmbHḹ鑴܀耀Uninstall</li><li>terate GmbHḹ鑴܀耀Uninstall </li><li>iterate GmbHῙ҅ഀ谀Ⅼ涄쿨´ </li><li>iterate GmbHத﷑⠀耀Microsoft </li><li>iterate GmbHၰ쇲℀谀Ⅼ擒쵈ɻ </li><li>iterate GmbHᕑ⮶ఀ谀Ⅼ掎꫘͘ </li><li>iterate GmbH᧠⢀㤀耀20240930 </li><li>iterate GmbH㐒㐀耀Microsoft </li><li>iterate GmbH㞊⬀耀Software </li><li>iterate GmbH䊵﫣Ѐ耀Microsoft </li><li>iterate GmbH䡍㡪ఀ谀Ⅼ燲̾ </li><li>iterate GmbH䩉鰔㐀耀Uninstall </li><li>iterate GmbH侬﬋㴀耀Software </li><li>iterate GmbH棙栓ࠀ耀Microsoft </li><li>iterate GmbH没➹⸀谀Ⅼ泒롰˧ </li><li>iterate GmbH滇쭆⸀耀ᆸ榪팀</li></ul> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Integrating ServiceNow with SAP (S4 Hana Cloud) ]]></title>
        <description><![CDATA[ If you don&#39;t think your client has SAP, think again. The German behemoth boasts that over 400,000 companies use its software. Bill McDermott, the current CEO of ServiceNow, served as SAP&#39;s very first American CEO up through 2019, when he left for ServiceNow. With such ]]></description>
        <link>https://relay.semaphorepartners.com/articles/integrating-servicenow-with-sap-s4-hana-cloud/</link>
        <guid isPermaLink="false">6745e4bfef40790001c791ec</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Wed, 27 Nov 2024 10:48:59 -0500</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/11/SAP_2011_logo.svg.png" medium="image"/>
        <content:encoded><![CDATA[ <p>If you don't think your client has SAP, think again. The German behemoth boasts that over 400,000 companies use its software. Bill McDermott, the current CEO of ServiceNow, served as SAP's very first American CEO up through 2019, when he left for ServiceNow. With such a storied background, it should be little surprise that business clients use SAP - quite possibly even including your own company! SAP is everywhere, so let's dive into why it's so important...</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/11/image-1.png" class="kg-image" alt="" loading="lazy" width="956" height="956" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/11/image-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/11/image-1.png 956w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Former SAP CEO, and current ServiceNow CEO, Bill McDermott</span></figcaption></figure><p>The core driver behind SAP's dominance as a platform is their ability to enhance back-end business processes. While SAP excels at these processes, the front-end can be a bit clunky and disconnected. That's why it's an ideal fit for integrating with ServiceNow using the Employee Service Center as the process origin-point and approval flow.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/11/image-2.png" class="kg-image" alt="" loading="lazy" width="960" height="540" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/11/image-2.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/11/image-2.png 960w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">ServiceNow's Employee Service Center</span></figcaption></figure><p>Unfortunately, despite excellent documentation, SAP's jungle of products and versions became quite the confusing mess to figure out. Having someone who’s familiar with the other tool and is willing to lend a hand is a huge asset when it comes to integration. One of the SAP Admins for our client was instrumental in helping us get this project done. </p><p><strong>The integration</strong></p><p>We began our work for the client with the goal of managing their SAP Purchase Order requests from within the ServiceNow interface.</p><p>This effort was focused around requesting a Purchase Order via the Employee Service Center in ServiceNow. In order to achieve this, we needed to introduce a Catalog Item which could display remote SAP items and eventually transmit new Purchase Orders back to SAP. Integrating SAP via Catalog Items enabled creation of powerful workflows, such as the handling of approvals entirely within ServiceNow, <em>before</em> anything ever gets touched on SAP</p><p><strong>Authentication</strong></p><p>The SAP API is session-based, in other words: before anything else, we must present our credentials and request a new session cookie. This posed an issue, because ServiceNow's user-friendly REST Flows do not expose cookies! The solution here was for us to leverage the lower-level RESTMessageV2 API using JavaScript, which exposed for us the very helpful getCookies method.</p><pre><code class="language-Javascript">var cookies = j2js(response.getCookies());

//Extracting context number from cookies
var contextCookiePrefix = "sap-usercontext=";
var maybeContextNumber = Stream.fromArray(cookies)
  .find(function (cookie) {
    return cookie.startsWith(contextCookiePrefix);
  })
  .map(function (contextCookie) {
    var cookieContent = contextCookie.slice(contextCookiePrefix.length);
    var cookieData = cookieContent.split(";")[0];
    var contextNumber = cookieData.trim().replace(/^sap-client=/, "");
    return contextNumber;
  });
if (maybeContextNumber.isEmpty()) {
  // This should never happen
  gs.error(
    new NiceError(
      "Server response OK but could not find context number given the following cookies: {0}"
    ),
    JSUtil.describeObject(cookies)
  );
}
//Looking for session cookie
var maybeSessionCookie = maybeContextNumber.flatMap(function (contextNumber) {
  return Stream.fromArray(cookies).find(function (cookie) {
    return cookie.startsWith("SAP_SESSIONID_NED_" + contextNumber + "=");
  });
});
</code></pre><p><strong>Content Length</strong></p><p>Another showstopper was the APl's mandatory requirement to pre-compute Content-Length headers on outgoing HTTP messages. This posed another problem because ServiceNow does variable substitution at the very last moment, <em>after</em> our Data Source's last chance to set Content-Length!</p><p>We eventually bypassed this issue by manually applying body variable substitution – in JavaScript – long before ServiceNow would normally do it. This required some fairly creative code to pull off:</p><pre><code class="language-Javascript">var templateParameters = {
  netPriceAmount: ritmGr.variables.product_cost,
  // ... and so forth. This is a mapping
  // Keys corresond to Outbound Message variables
  // Values correspond to Request Item variables
  glAccount: ritmGr.variables.gl_account.u_node_name.toString(), // "545208",
};

// Format template parameters as data acceptable by GlideStringUtil.substituteVariables
var jProperties = new Packages.java.util.Properties();
for (var key in templateParameters) {
  var value = String(templateParameters[key]);
  jProperties.put(key, value.replace('"', '\\"'));
  request.setStringParameter(key, value);
}
// Do variable substitution for the body early so we can accurately calculate content-length
var templateContent = request.getRequestBody();
var content = j2js(GlideStringUtil.substituteVariables(templateContent, jProperties));
// SAP blows up if the JSON payload contains whitespace, so guard against that here
var minifiedContent = JSON.stringify(JSON.parse(content));
request.setRequestBody(minifiedContent);
var contentLength = minifiedContent.length;
request.setRequestHeader('Content-Length', contentLength);</code></pre><p><strong>Data Sources</strong></p><p>Since the Catalog Item needed SAP data, we created multiple scripted Data Sources to pull over the data from SAP, leveraging as much of the Out of box REST Message functions as possible. </p><pre><code class="language-Javascript">var message = new sn_ws.RESTMessageV2('SAP - PO', 'Get Cost Center');
var resultStream = sappo_streamData(message);
resultStream.forEach(function(result) {
	import_set_table.insert({
		"currency": result["CostCntrCurr"],
		"rollup": result["RollUp"],
		"number": result["NodeName"],
		"name": result["NodeDesc"],
		"area": result["ControllingArea"],
		"id": result["ID"],
	});
});</code></pre><p>Although these elements were brought over for this Catalog Item, some of them were mapped to out of box tables  (Vendors -&gt; core_company, Cost Centers -&gt; cmn_cost_center...), and help supplement the existing user data that we were getting. </p><p><strong>Conclusion</strong></p><p>Integrating with SAP is possible, but not easy. However, if you (or your client) want ServiceNow to be THE customer-facing portal, then an integration to ERP tools  - such as SAP - <em>will</em> be a necessity. </p><p>Building an integration like this will enable you to harness ServiceNow’s Service Catalog, Portal, Workflow Engine, and notifications to maintain a consistent branding and messaging across all platforms, even though the final fulfillment of this request will still occur in SAP. </p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ UI Action Client/Server Interactions (without AJAX!) ]]></title>
        <description><![CDATA[ Do you hate AJAX Script Includes with a fiery passion? I do – they&#39;re ugly, error-prone, and full of annoying boilerplate! I consider the unfortunate reliance upon AJAX Script Includes within UI Actions to be one of ServiceNow&#39;s foundational sins. It simply should not be this hard ]]></description>
        <link>https://relay.semaphorepartners.com/articles/client-server-ui-actions-without-ajax/</link>
        <guid isPermaLink="false">671bef29c3e5340001344b85</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Christopher Crockett ]]></dc:creator>
        <pubDate>Mon, 28 Oct 2024 11:40:28 -0400</pubDate>
        <media:content url="" medium="image"/>
        <content:encoded><![CDATA[ <p>Do you hate AJAX Script Includes with a fiery passion? <strong>I do</strong> – they're ugly, error-prone, and full of annoying boilerplate! I consider the unfortunate reliance upon AJAX Script Includes within UI Actions to be one of ServiceNow's foundational sins. It simply should <em>not</em> be this hard to achieve basic user interactions when using perhaps the premiere design verb available to us within ServiceNow.</p><p>Frequently stumped by this issue, we've poured much time and thought into existing solutions and have created a novel solution worthy of a new place in the pantheon of UI Action design patterns. I'll reveal all below... but in order to get there we'll first need to cover the existing options &amp; limitations. <strong>Those already familiar should feel free to skip ahead past... <em>The Managerie</em>. Those simply searching for copyable templates can even skip directly to the bottom!</strong></p><h1 id="a-menagerie-of-extant-ui-actions">A Menagerie of Extant UI Actions</h1><h2 id="server-side-the-conventional-solution">Server-Side: The Conventional Solution</h2><p>The typical UI Action is easy to understand: simply click a button and some Server code gets triggered. These are the backbone of the UI Action paradigm and we'll call them "Server-Side UI Actions".</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-12-1.png" class="kg-image" alt="" loading="lazy" width="627" height="637" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/10/image-12-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-12-1.png 627w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">You can tell they're Server-Side because "Client" is unchecked!</em></i></figcaption></figure><p>Designers should use Server-Side UI Actions whenever possible, because all other approaches are necessarily more complex and therefore less desirable. The only reason to be using any other pattern – ours included – is if design requirements exceed the inherent limitations of Server-Side UI Actions. These limitations are born of the inherent architecture of Server-Side UI Actions, illustrated below...</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-1-1.png" class="kg-image" alt="" loading="lazy" width="783" height="662" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/10/image-1-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-1-1.png 783w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">These are called "Sequence Diagrams". They're read top-to-bottom!</em></i></figcaption></figure><p>As you can see, there are zero opportunities for the designer to insert user-facing interactions during the moment between UI Action activation and form submission. It's already too late by the time the "Server Script" in our diagram has a chance to run.</p><p>This limitation precludes many useful design techniques, such as:</p><ul><li>Modifying the form input <em>without</em> immediately submitting changes.</li><li>Interactive input validations <em>without</em> forcing the user to submit &amp; pray .</li><li>Inserting additional prompts/dialogs into the submission design flow.</li></ul><h2 id="client-side-a-different-key-for-a-different-lock">Client-Side: A Different Key For a Different Lock</h2><p>Client-Side UI Actions completely flip the script. They don't cause <em>any</em> Server-side code to run when clicked. Nothing even gets <em>submitted</em> to the Server!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-15-1.png" class="kg-image" alt="" loading="lazy" width="626" height="633" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/10/image-15-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-15-1.png 626w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Checked "Client" means Client-Side Code </em></i><span style="white-space: pre-wrap;">🤯</span></figcaption></figure><p>This is actually quite handy for the intended use-case: providing simple shortcuts and on-page doodads which make the user's life easier. It's a great pattern <em>if</em> design requirements don't demand any interaction with the Server...</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-5.png" class="kg-image" alt="" loading="lazy" width="879" height="638" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/10/image-5.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-5.png 879w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Note how there's no "</em></i><span style="white-space: pre-wrap;">Form Submit</span><i><em class="italic" style="white-space: pre-wrap;">" or "</em></i><span style="white-space: pre-wrap;">Server Script</span><i><em class="italic" style="white-space: pre-wrap;">", unlike the above Server-Side diagram.</em></i></figcaption></figure><p>When you get down to it, the core Server-Side and Client-Side UI Action types actually have very little functional overlap. This is something of a problem, because it is extremely common for designers to desire functionality from both paradigms in the same UI Action!</p><h2 id="combo-ui-actions-two-for-one-sort-of">"Combo" UI Actions: Two For One... Sort Of?</h2><p>"<em>Combo UI Action</em>" is a term of art invented for the purposes of this article. It's an undocumented yet common variant of the <em>Client-Side UI Action</em> which cleverly sneaks in the ability to execute Server-Side code – almost as if there were actually two different UI Actions belonging to the same button: one Client-Side, one Server-Side. Emphasis on <strong>"different"</strong>! There's a lot to unpack here, so let's first take a peek at the code...</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-16-1.png" class="kg-image" alt="" loading="lazy" width="741" height="810" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/10/image-16-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-16-1.png 741w" sizes="(min-width: 720px) 720px"><figcaption><i><em class="italic" style="white-space: pre-wrap;">See how there's some Server code hiding b that IF statement?</em></i></figcaption></figure><p>This technique allows for writing some code which runs client-side <em>and</em> some code which runs Server-Side. <strong>It sounds more useful than it actually is – the Server-Side code and the Client-Side code are completely isolated from each other and cannot communicate!</strong></p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-7.png" class="kg-image" alt="" loading="lazy" width="846" height="809" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/10/image-7.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-7.png 846w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Note how there's no way for data from "Client Script" to travel over to where "Server Script" lives.</em></i></figcaption></figure><p>This approach is useful if the intent is to attach some Client-Side pre-validation to an otherwise independent Server-Side UI Action... but it completely falls flat if, <em>for example</em>, the designer wishes to prompt the user for input and then handle that user-provided input via Server-Side code. <strong>It really <em>is</em> like have two completely separate UI Actions attached to the same user-facing button – the streams cannot be crossed!</strong></p><p>Time for a detour: how does this trick actually work? <strong>This is important: even if you've used this pattern before, it's important to understand the underlying mechanics!</strong><br><br>Let's start by examining how Server-Side UI Actions internally function:</p><ol><li>User clicks the button.</li><li>Button triggers the following client-side code <code>gsftSubmit(this)</code></li><li>The g_form is HTTP POSTed to the Server.</li><li>Code in the "Script" field is triggered on the Server to process the form data.</li></ol><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-28-1.png" class="kg-image" alt="" loading="lazy" width="611" height="77" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/10/image-28-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-28-1.png 611w"><figcaption><span style="white-space: pre-wrap;">The resulting button HTML generated for a Server-Side UI Action</span></figcaption></figure><p>Steps 1-3 are abstracted away and hidden from the designer, but they exist nevertheless! Now that we understand the mechanics, we can start to appreciate how surprisingly similar Client-Side UI Actions are under the hood:</p><ol><li>Code in the "Script" field is smuggled into the HTML page during initial load.</li><li>User clicks the button.</li><li>Button triggers client-side code from the "On Click" field<br>(<em>traditionally, this is used to call one of the functions smuggled in during Step 1</em>)</li></ol><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-29-1.png" class="kg-image" alt="" loading="lazy" width="551" height="94"><figcaption><span style="white-space: pre-wrap;">The resulting button HTML generated for a Client-Side UI Action</span></figcaption></figure><p>Client-Side UI Actions essentially have two special traits:</p><ul><li>A copy of your "Script" code gets smuggled into the HTML page.</li><li>Instead of defaulting to <code>gsftSubmit(this)</code> it uses your "On Click" code.</li></ul><p>That's it! <strong>These two changes alone are what make the two types different.</strong> Should one desire, it's entirely possible to create a fake "Client-Side UI Action" that's actually identical to a Server-Side UI Action by simply setting the "On Click" field to <code>gsftSubmit(this)</code> – recall that this is what would've happened automatically were "Client" left unchecked!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-17-1.png" class="kg-image" alt="" loading="lazy" width="617" height="586" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/10/image-17-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-17-1.png 617w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Whenever form submission triggers – no matter how – the Server </em></i><i><b><strong class="italic" style="white-space: pre-wrap;">will</strong></b></i><i><em class="italic" style="white-space: pre-wrap;"> run the "Script" code</em></i></figcaption></figure><p>Technically this hack <em>does</em> cause an ugly looking error to pop up in the browser console (<em>it tried and failed to run some smuggled Server-Side code</em>)... but we can get around the issue if we just wrap the Server-Side code in a protective IF statement!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-19-1.png" class="kg-image" alt="" loading="lazy" width="697" height="291" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/10/image-19-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-19-1.png 697w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Doesn't this look awfully familiar? </em></i><span style="white-space: pre-wrap;">🧐</span></figcaption></figure><p>Yup. That's the origin of the weird <code>if (typeof window === 'undefined')</code>. Neat!</p><h2 id="ajax-the-only-route-to-true-interactivity">AJAX: The Only Route to True Interactivity?</h2><p>Time for the elephant in the room. All of these patterns are <em>nice...</em> but they don't actually handle the most common use-case for Client-Side UI Actions: prompting the user for input and then using this input in Server-Side code. Historically, this has been the stomping ground of what we'll call "<em>AJAX UI Actions</em>".</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-21-1.png" class="kg-image" alt="" loading="lazy" width="807" height="749" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/10/image-21-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-21-1.png 807w" sizes="(min-width: 720px) 720px"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Triggering a Server-Side Script remotely via AJAX (80%+ Boilerplate) </em></i></figcaption></figure><p>"AJAX" in the context of ServiceNow specifically refers to a type of Script Include which can be remotely triggered from Client-Side scripts via the <a href="https://developer.servicenow.com/dev.do?ref=relay.semaphorepartners.com#!/reference/api/xanadu/client/c_GlideAjaxAPI">GlideAjax</a> API. It's the bog-standard approach to writing Server-Side code which can be remotely executed via Client-Side scripting.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-23.png" class="kg-image" alt="" loading="lazy" width="658" height="561" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/10/image-23.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-23.png 658w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">The Server-Side Script must be defined in a second application file (MORE BOILERPLATE </em></i><span style="white-space: pre-wrap;">😡</span><i><em class="italic" style="white-space: pre-wrap;">)</em></i></figcaption></figure><p>Frustratingly, <strong>adding even a small amount of AJAX to a given UI Action can double or even triple total lines of code</strong>. Perhaps even more annoyingly: AJAX Script Includes are <em>slow</em> – <strong>UI AJAX Actions can be forced to wait for several seconds mid-execution while the Server thinks!</strong></p><p>Despite these flaws, it is undeniable that AJAX Script Includes are incredibly useful tools whenever a requirement to dynamically interact back-and-forth with the Server based on user input arises...</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-8.png" class="kg-image" alt="" loading="lazy" width="862" height="823" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/10/image-8.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-8.png 862w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Adding AJAX Script Includes to a Client-Side UI Action unlocks User/Server interactions</em></i></figcaption></figure><p>Even so, isn't it kind of overkill if all that's needed is a <em>one-way</em> conversation that starts on the Client-Side and ends on the Server-Side? Why can't there be a happy middle with the simplicity of the "<em>Combo UI Action</em>" pattern for those usecases?</p><h1 id="one-way-ui-actions-a-new-way">"One-Way" UI Actions: A New Way</h1><p>We are proud to present a novel UI Action design pattern which achieves exactly this: the simplicity of "<em>Combo UI Actions</em>" + the Client/Server communication. A sort of "<em>One-Way</em>" connection which enables collecting user input on the Client-Side and then punts it over to the Server-Side for processing!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-24-1.png" class="kg-image" alt="" loading="lazy" width="680" height="1013" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/10/image-24-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-24-1.png 680w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Client-Side and Server-Side in the same UI Action, but with data passing!</em></i></figcaption></figure><p>We've effectively extended the aforementioned "<em>Combo UI Action</em>" solution by exploiting the HTTP form submission as a medium for data transport. This tweak eliminates the single greatest limitation of the "<em>Combo UI Action</em>" and unlocks the ability to elegantly handle many User/Server interactions which were previously only possible via AJAX Script Includes!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-26.png" class="kg-image" alt="" loading="lazy" width="897" height="929" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/10/image-26.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-26.png 897w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">AJAX begone!</span></figcaption></figure><p>As you've probably noticed, this new approach brings new limitations:</p><ul><li>Higher complexity than the traditional "Combo UI Action" pattern.</li><li>Server-Side only starts after Client-Side ends – No back-and-forth.</li></ul><p><strong>In other words: this isn't a perfect solution</strong> – don't reach for it unless it fits the usecase! Some rules of thumb to consider:</p><ul><li>If it's not necessary to pass data from Client-Side to Server-Side, use the simpler "<em>Combo UI Action</em>" pattern.</li><li>If it's necessary to make multiple request/interaction cycles within a single UI Action, use the more powerful "<em>AJAX Script Include</em>" design pattern.</li></ul><h2 id="copyable-templates-%F0%9F%A4%93">Copyable Templates 🤓</h2><p>The initial example presented is designed for maximum compatibility – it works regardless of whether the UI Action is present on a list or form page at the cost of extra boilerplate. The required boilerplate is <em>halved</em> if you don't need list compatibility!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/10/image-25.png" class="kg-image" alt="" loading="lazy" width="529" height="305"><figcaption><span style="white-space: pre-wrap;">🔥🔥 </span><i><em class="italic" style="white-space: pre-wrap;">Under 20 lines boilerplate </em></i><span style="white-space: pre-wrap;">🔥🔥</span></figcaption></figure><p><strong>Templates for common usecases such as Form-Only, List-Only, and Form+List are available for copying via<em> </em></strong><a href="https://gist.github.com/chaorace/4854fee109ec3d84ce464ee79df8ed04?ref=relay.semaphorepartners.com" rel="noreferrer"><strong><em>this gist</em></strong></a><strong>.</strong></p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Keeping tabs on your MID Server ]]></title>
        <description><![CDATA[ The ServiceNow MID Server is an integral part of a successful deployment, and its features and functionality have grown significantly over the years. It’s a crucial component to keep up and running. Many customers rely on the MID Server for integrations, Discovery, Service Mapping, Cloud Management, Event Management, Operational ]]></description>
        <link>https://relay.semaphorepartners.com/articles/keeping-tabs-on-your-mid-server/</link>
        <guid isPermaLink="false">67128f3f4911c90001a0cfe0</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Fri, 18 Oct 2024 12:44:32 -0400</pubDate>
        <media:content url="https://images.unsplash.com/photo-1558494949-ef010cbdcc31?crop&#x3D;entropy&amp;cs&#x3D;tinysrgb&amp;fit&#x3D;max&amp;fm&#x3D;jpg&amp;ixid&#x3D;M3wxMTc3M3wwfDF8c2VhcmNofDR8fGRhdGElMjBjZW50ZXJ8ZW58MHx8fHwxNzI5MjIxNDk5fDA&amp;ixlib&#x3D;rb-4.0.3&amp;q&#x3D;80&amp;w&#x3D;2000" medium="image"/>
        <content:encoded><![CDATA[ <p>The ServiceNow MID Server is an integral part of a successful deployment, and its features and functionality have grown significantly over the years. It’s a crucial component to keep up and running. Many customers rely on the MID Server for integrations, Discovery, Service Mapping, Cloud Management, Event Management, Operational Intelligence, Log Ingestion, and more. Therefore, it's essential that your MID Servers stay operational to prevent disruptions to the critical business processes that depend on them.</p><p>Most organizations have teams that support the infrastructure that keeps the actual servers up and running and often have event monitoring in place to ensure uptime and availability. However, it's always nice to have additional options and safeguards in place. Here are a few simplistic ways to keep tabs on your MID Server (most of which are already out-of-the-box and require minimal setup).</p><p>The ServiceNow instance sends a synthetic transaction via the Heartbeat probe to the MID Servers every five minutes. If the instance doesn’t detect a response from a MID Server, that server is marked as down. This will be important to keep in mind for the methods listed below. The heartbeat frequency can be adjusted by changing the scheduled job “MID Server Monitor.”</p><p>Here are some ways to keep tabs on your MID Server:</p><ol><li><strong>Notification</strong>: The MID Server table (<code>ecc_agent</code>) has a prebuilt notification that is out-of-the-box (OOB). All you need to do is provide a recipient. The notification fires when the MID Server status changes to "Down." This notification is already activated but doesn't know who to send it to. Simply navigate to it and provide a recipient.</li><li><strong>Lifecycle Event [mid_server_event]:</strong> This recently introduced table tracks MID Server events such as up, down, upgrading, invalidated, validated, and removed. This table gives you more of a timeline view of the state (including dates/times) compared to the MID Server table (<code>ecc_agent</code>), which only provides the current status. Access this table via direct navigation (<code>mid_server_event.list</code>), the left navigation (MID Server &gt; Profiles and Deployments &gt; MID Server Lifecycle Events), or the UI action on a MID Server labeled "Lifecycle Events" in the Related Links section.</li><li><strong>Resource Threshold Alerts</strong>: These alerts help monitor allocated CPU and memory. The configured alerts push data into the MID Server Issue (<code>ecc_agent_issue</code>) table. This method provides insight into potential issues before they impact your server’s performance.</li><li><strong>Application Insights</strong>: While this feature is being prepared for future deprecation in Xanadu, it’s worth mentioning if you need to trigger flows based on ECC queue thresholds. Application Insights is the best way to do this. You can request and install it via the ServiceNow Store, but you will need to request MetricBase via Support in order to use Application Insights. Once installed, you can set up thresholds based on specific ECC metrics you’re concerned with. This is a more advanced type of monitoring because it does more than simple lifecycle event tracking.</li><li><strong>Event Management</strong>: If you are licensed for ITOM Event Management, you can leverage its framework to monitor the MID Server and other areas of event management. Navigate to Event Management &gt; Administration &gt; Self-Health Monitoring. Many monitoring configurations exist out-of-the-box, but we are specifically going to look at the “MID Server Threshold Alerts.” These alerts create events and alerts based on how you’ve configured Event Management. You can use flows and other automations to create incidents or other ticket types based on your needs.</li></ol><p>For any of the methods listed above that place data into a table, you can take a variety of approaches based on complexity to meet your monitoring needs:</p><ol><li><strong>Notifications</strong></li><li><strong>Business Rules, Flows, or Workflows</strong><br>a. Use these to trigger notifications<br>b. Create incidents or create/update other ticket types</li><li><strong>Dashboards</strong></li></ol> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ ServiceNow: Arrow Functions (and more) Everywhere! ]]></title>
        <description><![CDATA[ Did you know? Starting in the Washington release, ServiceNow&#39;s Rhino engine now natively supports JavaScript Arrow Functions!

It works everywhere server-side code runs. Even in the Global scope without using the new &quot;Turn on ECMAScript 2021 (ES12) mode&quot; button.


How can this be??

Those of you ]]></description>
        <link>https://relay.semaphorepartners.com/articles/servicenow-upgraded-rhino/</link>
        <guid isPermaLink="false">66e3319405a9d00001c95f3b</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Christopher Crockett ]]></dc:creator>
        <pubDate>Thu, 12 Sep 2024 17:15:00 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2025/04/ArrowFunctions.png" medium="image"/>
        <content:encoded><![CDATA[ <p><em>Did you know?</em> Starting in the Washington release, ServiceNow's Rhino engine now <strong>natively</strong> supports JavaScript <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions?ref=relay.semaphorepartners.com" rel="noreferrer">Arrow Functions</a>!</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-6.png" class="kg-image" alt="" loading="lazy" width="875" height="395" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/09/image-6.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-6.png 875w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Even as recently as Vancouver, Arrow Functions wouldn't have worked...</em></i></figcaption></figure><p>It works everywhere server-side code runs. <strong>Even in the Global scope without using the new "<em>Turn on ECMAScript 2021 (ES12) mode</em>" button.</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-7.png" class="kg-image" alt="" loading="lazy" width="562" height="189"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Yet now they work! No special properties or scopes required.</em></i></figcaption></figure><h3 id="how-can-this-be">How can this be??</h3><p>Those of you following recent ServiceNow releases have probably already (<em>correctly</em>) intuited that this new development must be somehow related to the recent introduction of the new "ES12" JavaScript Mode. This is true... to an extent.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-9.png" class="kg-image" alt="" loading="lazy" width="665" height="136" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/09/image-9.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-9.png 665w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">The "JavaScript Mode" setting available via Application configuration.</em></i></figcaption></figure><p>Internally, ServiceNow uses an ancient JavaScript engine known as <a href="https://github.com/mozilla/rhino?ref=relay.semaphorepartners.com" rel="noreferrer">Rhino</a>. Rhino doesn't support most ES12 features, yet ServiceNow still somehow supports ES12... <strong>How is ServiceNow pulling off that magic trick? In short: </strong><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript?ref=relay.semaphorepartners.com#javascript_transpiling" rel="noreferrer"><strong>Transpilation</strong></a><strong>.</strong> Transpilers automatically convert modern code into legacy-compatible code – ServiceNow has a built-in auto-transpiler which automatically converts code wherever "ES12" mode has been enabled.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-11.png" class="kg-image" alt="" loading="lazy" width="609" height="165" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/09/image-11.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-11.png 609w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Babel is a popular transpiler. ServiceNow probably uses a different transpiler stack, though!</em></i></figcaption></figure><p>So, is that what's going on here? Did ServiceNow accidentally turn on their magic transpilation step <em>everywhere</em> and somehow never notice? <strong>Nope!</strong> That's definitely not the case because many other ES12 JavaScript features available remain unusable.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-12.png" class="kg-image" alt="" loading="lazy" width="871" height="446" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/09/image-12.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-12.png 871w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">If this were transpilation, we'd expect other non-Rhino syntax to also work!</em></i></figcaption></figure><p>So... what's going on here, if not transpilation? The answer is actually rather simple: <strong>ServiceNow's version of Rhino has been upgraded to a newer release – first 1.7.12 in Washington, then 1.7.14 in Xanadu.</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-13.png" class="kg-image" alt="" loading="lazy" width="1135" height="417" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/09/image-13.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/09/image-13.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-13.png 1135w" sizes="(min-width: 720px) 720px"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Up through Vancouver, we were (most likely) on version </em></i><i><b><strong class="italic" style="white-space: pre-wrap;">1.7R5</strong></b></i></figcaption></figure><p>We can prove this by checking if <code>Array.prototype.includes</code> is available. This is a newer Array method which only gained support starting in Rhino version <em>1.7.12</em>.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-21.png" class="kg-image" alt="" loading="lazy" width="844" height="401" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/09/image-21.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-21.png 844w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Just how long have we all been waiting for this moment??</em></i></figcaption></figure><p>The proof is in the pudding. As you can see below: <strong>Rhino has gained native support for many new language features which were previously impossible!</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-20.png" class="kg-image" alt="" loading="lazy" width="551" height="173"><figcaption><i><code spellcheck="false" style="white-space: pre-wrap;"><em class="italic">array.indexOf('foo') !== -1</em></code></i><i><em class="italic" style="white-space: pre-wrap;"> begone!</em></i></figcaption></figure><p>This long-overdue engine update can be chalked up to a happy side-effect of ServiceNow's ongoing work on the new "ES12" JavaScript transpilation mode:<br><strong>More engine features =&gt; Less transpilation =&gt; Better performance &amp; accuracy</strong></p><h3 id="whats-the-catch">What's The Catch?</h3><p><em>The catch</em> is that Rhino is still an old engine without full ES12 support. <strong>Even if <em>some</em> new features have become available, &gt;50% of all modern JavaScript features are still unavailable to Rhino</strong>. In other words: most modern features are <em>still</em> locked behind transpilation (<em>i.e. "ES12" mode</em>), Rhino upgrade notwithstanding.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-18.png" class="kg-image" alt="" loading="lazy" width="1143" height="431" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/09/image-18.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/09/image-18.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-18.png 1143w"><figcaption><i><em class="italic" style="white-space: pre-wrap;">Unfortunately, Rhino is still an old engine with many missing features</em></i></figcaption></figure><h3 id="how-do-i-know-whats-newly-available-in-my-instance">How do I know what's newly available in my Instance?</h3><p>Our research indicates two recent upgrades to the Rhino engine:</p><ul><li><strong>Washington</strong>: Rhino 1.7.12</li><li><strong>Xanadu</strong>: Rhino 1.7.14</li></ul><p>Consulting <a href="https://mozilla.github.io/rhino/compat/engines.html?ref=relay.semaphorepartners.com" rel="noreferrer">this table</a>, search for any features which were red in <strong>1.7R5</strong> and now green in your instance's Rhino version (<em>see directly above</em>). Easy peasy!</p><p>Not so easy? Well, dear reader, this must be your lucky day because as a <em>special gift just for you</em> I've already compiled a list and snuck it in right below 😁</p><ul><li><strong>Method Shorthand</strong><br> <code>{ foo(){ return 'foo!'; } }</code> vs <code>{ foo: function(){ return 'foo!'; } }</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Arrow Functions</strong></a><br><code>(foo) =&gt; foo.explode()</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Template Literals</strong></a><strong> </strong>(Xanadu+)<br><code>`One plus one equals ${ 1 + 1 }. Two plus two equals ${ 2 + 2 }`</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Array.from</strong></a><br><code>Array.from('foo') // ['f', 'o', 'o']</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Array.of</strong></a><br><code>Array.of(1, 2, 3) // [1, 2, 3]</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Array.prototype.copyWithin</strong></a><br><code>[0, 1, 2, 3, 4, 5].copyWithin(2, 0, 2) // [0, 1, 0, 1, 2, 5]</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Array.prototype.includes</strong></a><br><code>[1, 2, 3].includes(2) // true</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Array.prototype.find</strong></a><br><code>[1, 2, 3, 4].find((x) =&gt; x &gt; 2) // 3</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Array.prototype.findIndex</strong></a><br><code>['a', 'b', 'c'].find((x) =&gt; x.startsWith('b')) // 1</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Array.prototype.fill</strong></a><br><code>[1, 2, 3, 4, 5, 6].fill('X', 2, 4) // [1, 2, 'X', 'X', 'X', 6]</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cbrt?ref=relay.semaphorepartners.com" rel="noreferrer">Math.cbrt</a><br><code>Math.cbrt(64) // 4 (cube root)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.clz32</strong></a><br><code>Math.clz32(1) // 31 (number of leading zeroes in 32-bit binary)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cosh?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.cosh</strong></a><br><code>Math.cosh(2) // 3.7621956910836314 (hyperbolic Math.cos)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acosh?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.acosh</strong></a><br><code>Math.acosh(2) // 1.3169578969248166 (hyperbolic Math.acos)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/expm1?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.expm1</strong></a><br><code>Math.expm1(1) // 1.718281828459045 (Math.exp(X) - 1)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.hypot</strong></a><br><code>Math.hypot(3, 4) // 5 (hypotenuse)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.imul</strong></a><br><code>Math.imul(3, 3.5) // 9 (integer multiplication)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.log10</strong></a><br><code>Math.log10(1000) // 3 (base-10 logarithm)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.log2</strong></a><br><code>Math.log2(8) // 3 (base-2 logarithm)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.sign</strong></a><br><code>Math.sign(-10) // -1</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sinh?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.sinh</strong></a><br><code>Math.sinh(2) // 3.626860407847019 (hyperbolic Math.sin)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asinh?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.asinh</strong></a><br><code>Math.asinh(2) // 1.4436354751788103 (hyperbolic Math.asin)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tanh?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.tanh</strong></a><br><code>Math.tanh(1) // 0.7615941559557649 (hyperbolic Math.tan)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atanh?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.atanh</strong></a><br><code>Math.atanh(0.5) // 0.549306144334055 (hyperbolic Math.atan)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.fround</strong></a><br><code>Math.fround(5.05) // 5.050000190734863 (round to single precision)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Math.trunc</strong></a><br><code>Math.trunc (9.99) // 9</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Number.EPSILON</strong></a> (Xanadu+)<br><code>Number.EPSILON // 2.220446049250313e-16</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Number.MAX_SAFE_INTEGER</strong></a><br><code>Number.MAX_SAFE_INTEGER // 9007199254740991</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Number.MIN_SAFE_INTEGER</strong></a><br><code>Number.MIN_SAFE_INTEGER // -9007199254740991</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Number.isFinite</strong></a><br><code>Number.isFinite(Infinity) // false</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Number.isInteger</strong></a><br><code>Number.isInteger(0.5) // false</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Number.isSafeInteger</strong></a><br><code>Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Number.isNaN</strong></a><br><code>Number.isNaN(NaN) // true</code> <br><code>Number.isNaN('foo') // false (unlike global.isNaN)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Number.parseFloat</strong></a><br><code>Number.parseFloat('123.456') // 123.456 (same as global.parseFloat)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseInt?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Number.parseInt</strong></a><br><code>Number.parseFloat('123') // 123 (same as global.parseInt)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Object.assign</strong></a><br><code>var foo = { a: 1, b: 2 };<br>Object.assign(foo, { c: 3 }); // foo becomes { a: 1, b: 2, c: 3 };</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>Object.is</strong></a><br><code>Object.is(-0, +0) // false, unlike a === check</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint?ref=relay.semaphorepartners.com" rel="noreferrer">String.fromCodePoint</a> (Xanadu+)<br><code>String.fromCodePoint(9733) // '★'</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>String.raw</strong></a> (Xanadu+)<br><code>String.raw`¯\_(ツ)_/¯` // '¯\_(ツ)_/¯' (No escape necessary!)</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>String.prototype.codePointAt</strong></a><br><code>'☃★♲'.codePointAt(1) // 9733</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>String.prototype.includes</strong></a><br><code>'foobar'.includes('oba') // true</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>String.prototype.normalize</strong></a><br><code>'\u00F1'.normalize('NFC') === '\u006E\u0303'.normalize('NFC') // true</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>String.prototype.padStart</strong></a><br><code>'12345'.padStart(8, '0') // '00012345'</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>String.prototype.padEnd</strong></a><br><code>'12345'.padEnd(8, '0') // '12345000'</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>String.prototype.repeat</strong></a><br><code>'the end is never '.repeat(2) // 'the end is never the end is never'</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>String.prototype.startsWith</strong></a><br><code>'foobar'.startsWith('foo') // true</code></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith?ref=relay.semaphorepartners.com" rel="noreferrer"><strong>String.prototype.endsWith</strong></a><br><code>'foobar'.startsWith('bar') // true</code></li></ul> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ ServiceNow&#x27;s Key Management Framework - Resource Exchange Requests explained ]]></title>
        <description><![CDATA[ Again, I found myself confused at some of the documentation that ServiceNow had provided. Am I that dumb? Is it that bad? Honestly, probably a little bit of both.

While previously it was regarding the Oracle Cloud HCM - ServiceNow Integration Hub Spoke, this time it was the Key Management ]]></description>
        <link>https://relay.semaphorepartners.com/articles/servicenowkey-management-framework-resource-exchange-requests-explained/</link>
        <guid isPermaLink="false">66e3206405a9d00001c95ead</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Thu, 12 Sep 2024 17:03:58 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/bcb3678e4d134a06b469d14be1d2035e.jpg" medium="image"/>
        <content:encoded><![CDATA[ <p>Again, I found myself confused at some of the documentation that ServiceNow had provided. Am I that dumb? Is it that bad? Honestly, probably a little bit of both. </p><p>While previously it was regarding the <a href="https://relay.semaphorepartners.com/articles/oracle-cloud-hcm-servicenow-integration-hub-spoke/">Oracle Cloud HCM - ServiceNow Integration Hub Spoke</a>, this time it was the Key Management Framework. A customer had issues authenticating with their other tools after a clone down process, and we were on the hook to figure it out. ServiceNow Support had suggested running a Resource Exchange Request, and that's where we had most of our issues.</p><h2 id="what-is-a-resource-exchange-request">What is a Resource Exchange Request? </h2><p>Since each instance uses a different encryption context, the data from Production can't be read by Dev or Test, until the Keys are exchanged from Prod (Source) to Dev or Test (Target). </p><p>If your instances are using the KMF, then you'll need to run this after each clone, if its not run automatically (based on the <code>glide_encryption.auto_key_exchange.enabled</code> property). </p><p>The steps in <a href="https://docs.servicenow.com/bundle/washingtondc-platform-security/page/administer/key-management-framework/task/configure-key-exchange.html?ref=relay.semaphorepartners.com" rel="noreferrer">this article</a> are missing some pertinent details, so this is my attempt to add some clarity. </p><h2 id="roles"><strong>Roles</strong></h2><ul><li>You need the "<strong>sn_kmf.cryptographic_manager"</strong> role in order to see the links the left hand nav<ul><li>You can't give yourself the "<strong>sn_kmf.cryptographic_manager</strong>" role unless you have the "<strong>sn_kmf.admin</strong>" role. <ul><li>You can't give yourself the "<strong>sn_kmf.admin</strong>" role unless you have the "<strong>security_admin</strong>" role. <ul><li>Once you have the "<strong>security_admin</strong>" role, you need to click on the "Key Management Administration" LHN link to bring you to this role assignment page</li></ul></li></ul></li></ul></li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-1.png" class="kg-image" alt="" loading="lazy" width="1110" height="222" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/09/image-1.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/09/image-1.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-1.png 1110w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Key Management Administration in ServiceNow</span></figcaption></figure><p>Yes, even if you start off with security_admin, you still need to log in and out two more times.</p><h2 id="creating-the-request-in-the-target-instance">Creating the Request (in the target instance)</h2><ul><li>Exchange Frequency: Choose ad-hoc</li><li>Crypto Specifications<ul><li>We had 36 different ones. You can select them all on this one request form, but you do have to select them all individually. </li></ul></li><ul><li>Once you're done, use this client-side snippet so you can keep the values for another instance</li></ul><ul><ul><li><code>copy(g_form.getValue("u_crypto_specification_list"))</code></li></ul></ul><li><strong>Do NOT right-click</strong> save. I couldn't figure out why these weren't sending until getting on the phone with ServiceNow Support. When you are creating these records you HAVE to click "<strong>Submit Request</strong>". </li></ul><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-2.png" class="kg-image" alt="" loading="lazy" width="494" height="104"></figure><ul><li>Once the request is submitted, individual requests will be created for each  specific Crypto Specifications.</li></ul><h2 id="approving-the-request-in-the-source-instance">Approving the Request (in the source instance)</h2><ul><li>If there are a lot of requests, it may take some time for them to show up in the source instance. <ul><li>Some Crypto specifications won't be found in the source instance, and that's ok. Those won't need to be approved. Here you'll only focus on the ones that are "Pending Approval'. </li><li>Out of the 36 that we requested, only 15 were found in the source instance.</li></ul></li><li>You'll need to approve each individual crypto specification. </li><li>Similarly, when you approve the request you need to change the Status to "Request Approved" manually, and then click "Update Request". DO NOT click Update. </li></ul><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-4.png" class="kg-image" alt="" loading="lazy" width="1848" height="200" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/09/image-4.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/09/image-4.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2024/09/image-4.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-4.png 1848w" sizes="(min-width: 720px) 720px"></figure><h2 id="confirming-the-request-in-the-target-instance">Confirming the Request (in the target instance)</h2><ul><li>Once a request is approved in the source instance, you should see the request in the target instance also move to a status of Request Approved with a few minutes.</li><li>Out of the 15 requests that were approved, we had one request that gave a status of "Error on Local Instance". But when we re-requested it again, it was able to be sent and approved successfully. </li></ul><h2 id="other-options">Other Options</h2><p>If a key exchange isn't a viable option for you or your customer, then you can also have the option it rekey during the clone instead. Read more <a href="https://docs.servicenow.com/bundle/utah-platform-security/page/administer/key-management-framework/reference/kmf-key-exchange-overview.html?ref=relay.semaphorepartners.com">here</a>.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-5.png" class="kg-image" alt="" loading="lazy" width="1860" height="1154" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/09/image-5.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/09/image-5.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2024/09/image-5.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/09/image-5.png 1860w" sizes="(min-width: 720px) 720px"></figure><p></p><p></p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Oracle Cloud HCM - ServiceNow Integration Hub Spoke ]]></title>
        <description><![CDATA[ The words of doom: &quot;No, we should be all set. There&#39;s a spoke for that&quot;


The problem

Sometimes it is as easy as plugging in credentials, sometimes it&#39;s not. This time it was not. ServiceNow has great documentation, especially compared to other solutions. It& ]]></description>
        <link>https://relay.semaphorepartners.com/articles/oracle-cloud-hcm-servicenow-integration-hub-spoke/</link>
        <guid isPermaLink="false">66d0df061056100001036433</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Tue, 03 Sep 2024 16:45:35 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/08/Oracle_HCM-Human_Capital_Management_Cloud.png" medium="image"/>
        <content:encoded><![CDATA[ <p>The words of doom: "No, we should be all set. There's a spoke for that"</p><h2 id="the-problem">The problem</h2><p>Sometimes it is as easy as plugging in credentials, sometimes it's not. This time it was not.  ServiceNow has great documentation, especially compared to other solutions. It's part of what makes them the king. But that's also what makes it so frustrating when they don't have documentation, or their documentation is missing a step. That's what made this integration so hard. </p><p>The client needed to integrate with Oracle Cloud HCM, and the ServiceNow Docs (<a href="https://docs.servicenow.com/bundle/xanadu-integrate-applications/page/administer/integrationhub/concept/oracle-hcm.html?ref=relay.semaphorepartners.com">Oracle HCM Cloud Spoke</a><a href="https://docs.servicenow.com/bundle/xanadu-integrate-applications/page/administer/integrationhub-store-spokes/task/setup-oracle-fin-cloud.html?ref=relay.semaphorepartners.com"></a><a href="https://docs.servicenow.com/bundle/xanadu-integrate-applications/page/administer/integrationhub-store-spokes/task/set-up-the-oracle-hcm-spoke.html?ref=relay.semaphorepartners.com"></a>) didn't quite have the detail I needed. </p><ul><li>The set-up has a lot of complex steps, and some that we had to reply on another System Admin for<ul><li>Create a keystore, upload it, create a password, send it to Oracle etc...</li><li>Give the oracle user the correct roles (like 20 of them)</li></ul></li><li>The steps referenced a JWT provider and Key, but didn't specifically mention them</li><li>The iss/sub values also weren't specified</li></ul><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/08/image-2.png" class="kg-image" alt="" loading="lazy" width="612" height="222" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/08/image-2.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/08/image-2.png 612w"><figcaption><span style="white-space: pre-wrap;">Unhelpful hover overs</span></figcaption></figure><p></p><h2 id="what-was-helpful">What was helpful</h2><ul><li>Instead of using an entire flow, we tried testing with just a small part of it<ul><li>The <strong>Get Oracle Cloud HCM Object Metadata</strong> action, using departments as the test object</li></ul></li><li>I also tried to use Rapid API (formerly Paw) as an additional option to debug</li><li>I also love using diagrams to help step through ServiceNow's text documentation to ensure I didn't miss a step, and to also see where the output of one activity feeds into another <ul><li>Whimsical is my tool of choice</li></ul></li></ul><p></p><h2 id="what-were-the-missing-steps">What were the missing steps:</h2><ol><li>We had to create JWT Provider and Keys (in the Global scope), which wasn't mentioned in the docs</li><li>Use those values in the Connection configuration</li><li>Make sure that the provider has the correct iss (Issue entered into Oracle HCM) and sub (User name of account used to log into Oracle HCM) values</li><li>Make sure the names of the JWT Provider and JWT Keys are unique. They're only referenced by name, but there's some automation that's setting the and that the <strong>sn_oracle_hcm_spk.glide.hub.jwtprovidersysid</strong> property with a sys_id</li></ol><h2 id="conclusion">Conclusion</h2><p>Maybe this will help you, but maybe it won't. But I know that next time I encounter this, I would have forgotten everything I learned so that's why I'm writing it down. </p><p>Let me know if this did help you, or if you have any questions on this spoke or a similar one. You can reach me at <a href="mailto:toby@semaphorepartners.com" rel="noreferrer">toby@semaphorepartners.com</a></p><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/08/image.png" class="kg-image" alt="" loading="lazy" width="1998" height="1510" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/08/image.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/08/image.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2024/08/image.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/08/image.png 1998w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Diagram of steps needed to make this integration work</span></figcaption></figure> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Reporting on Field Usage ]]></title>
        <description><![CDATA[ Often times on the ServiceNow platform, you want to look at the use of columns on a table. While you could easily build a report, that presents a challenge because you want to slice across all of the fields on a table rather than looking at a distinct set of ]]></description>
        <link>https://relay.semaphorepartners.com/articles/reporting-on-field-usage/</link>
        <guid isPermaLink="false">66bb93b35c1e8c00017b93dc</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Tue, 13 Aug 2024 14:33:26 -0400</pubDate>
        <media:content url="https://images.unsplash.com/photo-1688909906484-738d78601884?crop&#x3D;entropy&amp;cs&#x3D;tinysrgb&amp;fit&#x3D;max&amp;fm&#x3D;jpg&amp;ixid&#x3D;M3wxMTc3M3wwfDF8c2VhcmNofDUyfHxkYXRhYmFzZXxlbnwwfHx8fDE3MjM1NzMzOTF8MA&amp;ixlib&#x3D;rb-4.0.3&amp;q&#x3D;80&amp;w&#x3D;2000" medium="image"/>
        <content:encoded><![CDATA[ <p>Often times on the ServiceNow platform, you want to look at the use of columns on a table. While you could easily build a report, that presents a challenge because you want to slice across all of the fields on a table rather than looking at a distinct set of values across one field on the table.</p><p>I am sure you have your own reasons for this type of report, but here are some  potential uses:</p><ol><li>Platform Hygiene</li><li>Removing fields with little adoption / use</li><li>Removing tables with little adoption / use<br> </li></ol><p>The reporting engine doesn't really allow for this type of reporting. But surely this must be possible - right?</p><p>The platform has a feature that has a slightly different intended use but can be leveraged to assist with this type of reporting.</p><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">⚠️</div><div class="kg-callout-text">This is an undocumented feature. Use at your own risk, use in a non-production environment.</div></div><p>Enter <code>sys_column_stats_registry</code> [Column Statistics Registry]. This table is where you specify which table, and which field(s) you want to generate a report for. </p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/08/CleanShot-2024-08-13-at-13.33.39@2x.png" class="kg-image" alt="" loading="lazy" width="1432" height="598" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/08/CleanShot-2024-08-13-at-13.33.39@2x.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/08/CleanShot-2024-08-13-at-13.33.39@2x.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/08/CleanShot-2024-08-13-at-13.33.39@2x.png 1432w" sizes="(min-width: 720px) 720px"></figure><ol><li>Specify the table name, if you are working with a table that is extended you will need to input both the parent and child class.</li><li>Specify a specific field name or use the wildcard <code>*</code> to scan all fields on the table</li><li>Submit the record and return to the list of records</li><li>Use the List Related Link: "Collect statistics now" which will invoke a progress worker.</li></ol><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/08/CleanShot-2024-08-13-at-13.36.07@2x.png" class="kg-image" alt="" loading="lazy" width="2000" height="505" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/08/CleanShot-2024-08-13-at-13.36.07@2x.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/08/CleanShot-2024-08-13-at-13.36.07@2x.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2024/08/CleanShot-2024-08-13-at-13.36.07@2x.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/08/CleanShot-2024-08-13-at-13.36.07@2x.png 2172w" sizes="(min-width: 720px) 720px"></figure><ol start="5"><li>Visit the <code>sys_column_stats</code> table to view the collected results. Depending on your table size, this could take some time for the progress worker to complete.</li></ol><h2 id="results">Results</h2><p>On the <code>sys_column_stats</code> table the statistics are displayed:</p><p><strong>Null count: </strong>fields with no value set</p><p><strong>Not null count: </strong>fields with a value set</p><p><strong>Cardinality: </strong>a count of distinct values in a table</p><p>I find it best to join this data with the dictionary data, this way you have access to the field type and the field label.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/08/CleanShot-2024-08-13-at-14.15.42@2x.png" class="kg-image" alt="" loading="lazy" width="2000" height="1029" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/08/CleanShot-2024-08-13-at-14.15.42@2x.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/08/CleanShot-2024-08-13-at-14.15.42@2x.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2024/08/CleanShot-2024-08-13-at-14.15.42@2x.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/08/CleanShot-2024-08-13-at-14.15.42@2x.png 2000w" sizes="(min-width: 720px) 720px"></figure><p>This is an excellent tool to have. Give it a try and I'd love to hear your use cases for this!</p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Read Time for Knowledge ]]></title>
        <description><![CDATA[ If you frequent medium.com, wsj.com, or nytimes.com typically you are provided a &quot;time to read&quot; at the top of the article. In today’s busy world, it’s helpful to know if you’ve just got a few minutes to spare on your reading. This ]]></description>
        <link>https://relay.semaphorepartners.com/articles/read-time-for-knowledge/</link>
        <guid isPermaLink="false">66a27cd9bb333c0001f6a0f8</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Thu, 01 Aug 2024 00:00:00 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/08/time-to-read-v3.png" medium="image"/>
        <content:encoded><![CDATA[ <p>If you frequent medium.com, wsj.com, or nytimes.com typically you are provided a "time to read" at the top of the article. In today’s busy world, it’s helpful to know if you’ve just got a few minutes to spare on your reading. This got me thinking: Why can't we have something similar for Knowledge content in Service Portal/Employee Center?</p><p>The calculation is actually pretty simple. Most of these sites utilize the adult average word per minutes to calculate the read time. I decided to whip together a quick widget.</p><h2 id="html-template">HTML Template</h2><p>I am using a basic bootstrap panel to match the Service Portal/Employee Center theme and styling.</p><pre><code class="language-html">&lt;div class="panel panel-default b"&gt;
  &lt;div class="panel-heading text-center"&gt;
    &lt;h3&gt;
    {{c.ttr_mins}} {{c.minsLabel}} {{c.readSuffix}}
  &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<h2 id="client-script">Client Script</h2><p>Here is where the logic lives for the actual calculations</p><pre><code class="language-js">api.controller=function() {
  /* widget controller */
  var c = this;
	
	c.minsLabel = c.options.mins_label || "minute";
	c.readSuffix = c.options.read_label_suffix || "read";
	
	var wordsPerMinute = c.options.words_per_minute || 210;
	var kbText = angular.element('.kb-article-content').text();
	var words = kbText.trim().split(/\s+/g).length,
			wps = wordsPerMinute / 60,
			readingTimeSeconds = words / wps,
			mins = Math.floor(readingTimeSeconds / 60),
			seconds = Math.round(readingTimeSeconds % 60)
	
	c.ttr_mins = mins;

};
</code></pre>
<p>That is pretty much it, I would love to hear if this is useful or if you have any potential suggestions for improvements.</p><h2 id="how-to-use">How to use</h2><ol><li>Download the widget XML and import it or copy paste the HTML &amp; Client Script above.</li><li>Add the widget to your Knowledge page (typically <em>kb_article or esc_kb_article</em>) here is the link to the designer page <code>/$spd.do#/esc/editor/esc_kb_article_view/6f2c780e1beb0e5000a00fe0604bcb95</code></li><li>The widget includes an option schema for changing the words per minute.</li></ol><h2 id="result">Result</h2><figure class="kg-card kg-gallery-card kg-width-wide"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/CleanShot-2024-07-25-at-12.37.07@2x-2.png" width="2000" height="1843" loading="lazy" alt="" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/07/CleanShot-2024-07-25-at-12.37.07@2x-2.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/07/CleanShot-2024-07-25-at-12.37.07@2x-2.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2024/07/CleanShot-2024-07-25-at-12.37.07@2x-2.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/CleanShot-2024-07-25-at-12.37.07@2x-2.png 2312w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/CleanShot-2024-07-25-at-12.37.17@2x-2.png" width="622" height="314" loading="lazy" alt="" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/07/CleanShot-2024-07-25-at-12.37.17@2x-2.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/CleanShot-2024-07-25-at-12.37.17@2x-2.png 622w"></div><div class="kg-gallery-image"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/CleanShot-2024-07-25-at-12.48.54@2x.png" width="592" height="518" loading="lazy" alt=""></div></div></div></figure><h2 id="download"><br>Download</h2><div class="kg-card kg-signup-card kg-width-regular " data-lexical-signup-form="" style="background-color: #F0F0F0; display: none;">
            
            <div class="kg-signup-card-content">
                
                <div class="kg-signup-card-text kg-align-center">
                    <h2 class="kg-signup-card-heading" style="color: #000000;"><span style="white-space: pre-wrap;">Subscribe to gain access to the widget.</span></h2>
                    <p class="kg-signup-card-subheading" style="color: #000000;"><span style="white-space: pre-wrap;">Your go-to source for the latest insights, trends, and innovations in the world of ServiceNow</span></p>
                    
        <form class="kg-signup-card-form" data-members-form="signup">
            
            <div class="kg-signup-card-fields">
                <input class="kg-signup-card-input" id="email" data-members-email="" type="email" required="true" placeholder="Your email">
                <button class="kg-signup-card-button kg-style-accent" style="color: #FFFFFF;" type="submit">
                    <span class="kg-signup-card-button-default">Subscribe</span>
                    <span class="kg-signup-card-button-loading"><svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
        <g stroke-linecap="round" stroke-width="2" fill="currentColor" stroke="none" stroke-linejoin="round" class="nc-icon-wrapper">
            <g class="nc-loop-dots-4-24-icon-o">
                <circle cx="4" cy="12" r="3"></circle>
                <circle cx="12" cy="12" r="3"></circle>
                <circle cx="20" cy="12" r="3"></circle>
            </g>
            <style data-cap="butt">
                .nc-loop-dots-4-24-icon-o{--animation-duration:0.8s}
                .nc-loop-dots-4-24-icon-o *{opacity:.4;transform:scale(.75);animation:nc-loop-dots-4-anim var(--animation-duration) infinite}
                .nc-loop-dots-4-24-icon-o :nth-child(1){transform-origin:4px 12px;animation-delay:-.3s;animation-delay:calc(var(--animation-duration)/-2.666)}
                .nc-loop-dots-4-24-icon-o :nth-child(2){transform-origin:12px 12px;animation-delay:-.15s;animation-delay:calc(var(--animation-duration)/-5.333)}
                .nc-loop-dots-4-24-icon-o :nth-child(3){transform-origin:20px 12px}
                @keyframes nc-loop-dots-4-anim{0%,100%{opacity:.4;transform:scale(.75)}50%{opacity:1;transform:scale(1)}}
            </style>
        </g>
    </svg></span>
                </button>
            </div>
            <div class="kg-signup-card-success" style="color: #000000;">
                Email sent! Check your inbox to complete your signup.
            </div>
            <div class="kg-signup-card-error" style="color: #000000;" data-members-error=""></div>
        </form>
        
                    <p class="kg-signup-card-disclaimer" style="color: #000000;"><span style="white-space: pre-wrap;">No spam. Unsubscribe anytime.</span></p>
                </div>
            </div>
        </div><!--members-only--><div class="kg-card kg-file-card"><a class="kg-file-card-container" href="https://relay.semaphorepartners.com/content/files/2024/07/Knowledge_Read_Time_Semaphore-Partners.xml" title="Download" download=""><div class="kg-file-card-contents"><div class="kg-file-card-title">Knowledge_Read_Time_Semaphore Partners</div><div class="kg-file-card-caption">Widget XML for Service Portal/Employee Center</div><div class="kg-file-card-metadata"><div class="kg-file-card-filename">Knowledge_Read_Time_Semaphore Partners.xml</div><div class="kg-file-card-filesize">2 KB</div></div></div><div class="kg-file-card-icon"><svg viewBox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"></polyline><line class="a" x1="12" y1="6.75" x2="12" y2="18"></line><circle class="a" cx="12" cy="12" r="11.25"></circle></svg></div></a></div> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Recent Posts ]]></title>
        <description><![CDATA[  ]]></description>
        <link>https://relay.semaphorepartners.com/</link>
        <guid isPermaLink="false">66a16f0845563d0001cab862</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Wed, 24 Jul 2024 17:17:48 -0400</pubDate>
        <media:content url="" medium="image"/>
        <content:encoded><![CDATA[  ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Relay ]]></title>
        <description><![CDATA[ Relay is your go-to source for the latest insights, trends, and innovations in the world of ServiceNow. Brought to you by Semaphore Partners, a boutique consulting firm dedicated to delivering high-quality and attentive ServiceNow implementations, Relay aims to keep you at the forefront of digital transformation.

Our blog dives deep ]]></description>
        <link>https://relay.semaphorepartners.com/</link>
        <guid isPermaLink="false">66a157ac45563d0001cab818</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Wed, 24 Jul 2024 15:36:17 -0400</pubDate>
        <media:content url="" medium="image"/>
        <content:encoded><![CDATA[ <p>Relay is your go-to source for the latest insights, trends, and innovations in the world of ServiceNow. Brought to you by Semaphore Partners, a boutique consulting firm dedicated to delivering high-quality and attentive ServiceNow implementations, Relay aims to keep you at the forefront of digital transformation.</p><p>Our blog dives deep into the intricacies of ServiceNow, providing expert analysis, practical tips, and real-world case studies to help you navigate and maximize the platform's potential. Whether you're an administrator, developer, or ServiceNow professional, Relay offers valuable perspectives to enhance your ServiceNow journey.</p><p>Stay tuned to Relay for regular updates, industry news, and thought leadership that will keep you connected to the pulse of ServiceNow excellence.</p><div class="kg-card kg-button-card kg-align-center"><a href="#/portal/signup/free" class="kg-btn kg-btn-accent">Stay Informed</a></div><div class="kg-card kg-button-card kg-align-center"><a href="https://relay.semaphorepartners.com/articles" class="kg-btn kg-btn-accent">Blog</a></div> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Navigating by URL ]]></title>
        <description><![CDATA[ Navigate by URL, including Service Portal, and Workspaces ]]></description>
        <link>https://relay.semaphorepartners.com/articles/navigating-by-url/</link>
        <guid isPermaLink="false">66a14ca445563d0001cab7ec</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Wed, 24 Jul 2024 14:55:49 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/URL.png" medium="image"/>
        <content:encoded><![CDATA[ <p></p><h2 id="left-hand-navigation"><strong>Left Hand Navigation</strong></h2><p><code>table_name.do</code> - new form in existing frame</p><p><code>table_name.form</code> - new form in existing frame</p><p><code>table_name.list</code> - list in existing frame</p><p><code>table_name.LIST</code> - list in new window</p><p><code>table_name.FILTER</code> - list in new window, but <code>sysparm_filter_only=true</code></p><p><code>table_name.CONFIG</code> - Configure All in new window</p><h2 id="url-navigation-fulfiller-view"><strong>URL Navigation [Fulfiller view]</strong></h2><p><code>{TABLE}_list.do?nav_to.do?uri=sysparm_query={QUERY}</code> - List View of a Table</p><p><code>{TABLE}_list.do?sysparm_query={QUERY}</code> - List View of a Table</p><p><code>{TABLE}.do?sys_id={SYS_ID}</code> - Record</p><p><code>{TABLE}.do?sys_id={NUMBER}</code> - Works for most tasks (based on the display value)</p><h2 id="url-navigation-portal-view"><strong>URL Navigation [Portal view]</strong></h2><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">💡</div><div class="kg-callout-text">Navigation is possible to a page or portal</div></div><h3 id="navigation-by-portal">Navigation by Portal</h3><p>Navigating by Portal Navigating by portal displays the portal including the header, footer, and theme defined by the portal.</p><p><code>{PORTAL_SUFFIX}?id={PAGE}</code> [ex: tickets, sc_home, index]</p><p><code>{PORTAL_SUFFIX}?id={PAGE}&amp;sys_id={RECORD ID}</code> [ex: sc_category, sc_cat_item]</p><p><code>{PORTAL_SUFFIX}?id={PAGE}&amp;sys_id={RECORD ID}&amp;table{table}=</code> [ex: ticket]</p><h3 id="navigating-by-page">Navigating by Page</h3><p>Navigating by the page excludes the header/footer and theme. This is useful for standalone widget pages.</p><p><code>$sp.do?id={PAGE}&amp;param=1</code></p><p><strong>Other URL Parameters</strong></p><ul><li><code>sysparm_view</code> use a specific view to load the list or form</li><li><code>sysparm_filter_only</code> (Do not fetch the list contents useful if the table is large and will take a while to be loaded ex: sys_log, sys_email, etc...)</li><li><code>sysparm_order</code> specifies field to sort by</li><li><code>sysparm_order_direction</code> specifies the sorting direction (desc, asc)</li><li><code>sysparm_group_sort</code><strong> </strong>sort a list that is grouped (COUNT, COUNTDESC)</li></ul><h2 id="url-navigation-workspacesexperiences"><strong>URL Navigation [Workspaces/Experiences]</strong></h2><p>Workspaces have a similar but slightly different pattern, additionally since pages and parameters are defined on page by page level, they differ between workspaces.</p><p><strong>Base Workspace/Experience Suffix:</strong></p><p>Workspaces created by ServiceNow will have the <strong>/now/</strong> suffix appended after the instance URL.</p><p><code>/<strong>now</strong>/{WS_SUFFIX}/{PAGE_ID}/{TABLE}/{SYS_ID}</code> [ex: /now/sow/record/interaction/a513653f1bcd0e1000a00fe0604bcbe2]</p><p></p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Related List Queries ]]></title>
        <description><![CDATA[ A way to easily join a related table into an existing query ]]></description>
        <link>https://relay.semaphorepartners.com/articles/related-list-queries/</link>
        <guid isPermaLink="false">66a1467a7a30c20001b64dbe</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Wed, 24 Jul 2024 14:33:06 -0400</pubDate>
        <media:content url="https://images.unsplash.com/photo-1633412802994-5c058f151b66?crop&#x3D;entropy&amp;cs&#x3D;tinysrgb&amp;fit&#x3D;max&amp;fm&#x3D;jpg&amp;ixid&#x3D;M3wxMTc3M3wwfDF8c2VhcmNofDF8fHNxbHxlbnwwfHx8fDE3MjE4NDUzOTN8MA&amp;ixlib&#x3D;rb-4.0.3&amp;q&#x3D;80&amp;w&#x3D;2000" medium="image"/>
        <content:encoded><![CDATA[ <h2 id="what-are-they">What are they?</h2><p>A way to easily join a related table into an existing query</p><p>Some examples</p><ul><li><strong>Users with 1+ Open Incident</strong><ul><li>/sys_user_list.do?sysparm_query=RLQUERYincident.caller_id,&gt;=1^active=true^ENDRLQUERY</li></ul></li><li><strong>Users with 0 Active Incidents</strong><ul><li>/sys_user_list.do?sysparm_query=RLQUERYincident.caller_id,=0^active=true^ENDRLQUERY</li></ul></li><li><strong>Users with &gt;1 Computer Assigned to Them</strong><ul><li>/sys_user_list.do?sysparm_query=RLQUERYcmdb_ci_computer.assigned_to,&gt;1^ENDRLQUERY</li></ul></li></ul><h2 id="how-do-i-get-to-them">How do I get to them?</h2><p>I’m not as familiar with the structure of them yet, so instead of writing them from memory, I usually build them in the Reporting module first and then copy and paste it wherever I need them.</p><ol><li>Go to a list view, and jump from that query into the reporting module by clicking “Bar Chart” or “Pie Chart”</li><li>Once you’re there, expand the filter to show the “Related List Conditions” section</li><li>Run the report</li><li>Click on a bar to jump back into the list view</li><li>Right-click and copy the query and paste wherever you need it</li></ol><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/ContextMenu.png" width="706" height="966" loading="lazy" alt="" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/07/ContextMenu.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/ContextMenu.png 706w"></div><div class="kg-gallery-image"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/CopyQuery.png" width="1216" height="446" loading="lazy" alt="" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/07/CopyQuery.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/07/CopyQuery.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/CopyQuery.png 1216w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/Filter.png" width="1754" height="1446" loading="lazy" alt="" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/07/Filter.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/07/Filter.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2024/07/Filter.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/Filter.png 1754w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption><p dir="ltr"><span style="white-space: pre-wrap;">Right click a field header to jump straight from a list view to the reporting module, Click the Filter and then expand the Related List Condition section, All active users with one or more active Incident</span></p></figcaption></figure><pre><code class="language-jsx">active=true^RLQUERYincident.caller_id,&gt;=1^active=true^ENDRLQUERY^active=1
</code></pre><h2 id="how-are-they-structured">How are they structured?</h2><pre><code class="language-jsx">RLQUERY${RELATED_TABLE}.${RELATED_FIELD_NAME},${OPERATOR}${LIMIT}^ENDRLQUERY
</code></pre><p><strong>Example:</strong></p><pre><code class="language-jsx">RLQUERYincident.caller_id,&gt;=1^active=true^ENDRLQUERY
</code></pre><p><strong>RELATED_TABLE</strong>: The related table you’re looking to join from <em>(ex: incident)</em></p><p><strong>RELATED_FIELD_NAME</strong>: The table you’re looking to query from, this should point to the table you’re querying from <em>(ex: caller_id)</em></p><p><strong>OPERATOR</strong>: &gt;, &gt;= , =, &lt;, &lt;=</p><p>LIMIT: Can be any number</p><h2 id="do-they-work-everywhere">Do they work everywhere?</h2><p>Kind of. They work via the URL, and they work in scripts. I’ve even used them in Performance Analytics Indicators and Indicator Sources.</p><p>However, I’ve had mixed success using them in Reference Qualifiers. As it turns out, if you’re using them in a Reference Qualifier you can ONLY use the Related List Query.</p><p>For example, this won’t work as a reference qualifier for the sys_user table. This would instead show all active users.</p><pre><code class="language-jsx">active=true^RLQUERYincident.caller_id,&gt;=1^active=true^ENDRLQUERY
</code></pre><p>But this will work as a reference qualifier for the sys_user table</p><pre><code class="language-jsx">RLQUERYincident.caller_id,&gt;=1^active=true^ENDRLQUERY^EQ
</code></pre> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Notification Helper ]]></title>
        <description><![CDATA[ Introduction

Out of box, there’s 17 different notifications for the incident table. What are they for? What do they look like? Who do they go to? When do they go out? Do you want to open all 17 pages and preview them one by one in 17 different tabs? ]]></description>
        <link>https://relay.semaphorepartners.com/articles/notification-helper/</link>
        <guid isPermaLink="false">66a122ba7a30c20001b64cec</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Wed, 24 Jul 2024 11:51:04 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/Screenshot-2024-07-24-at-11.24.23-AM-1-1.png" medium="image"/>
        <content:encoded><![CDATA[ <h2 id="introduction">Introduction</h2><p>Out of box, there’s 17 different notifications for the incident table. What are they for? What do they look like? Who do they go to? When do they go out? Do you want to open all 17 pages and preview them one by one in 17 different tabs? <strong>Probably not!</strong></p><p>This helper will show you all of them on one page so you can review them to ensure you have consistent content, triggers, links for all of your notifications.</p><h2 id="what-does-it-do">What does it do?</h2><ul><li>Previews multiple notifications from one page</li></ul><h2 id="what%E2%80%99s-in-it">What’s in it?</h2><ul><li>Portal Page (ndoc)</li><li>Widget (notification_doc)</li><li>And all the other good stuff so that the Widget shows up on the Page<ul><li>Instance</li><li>Container</li><li>Row</li><li>Column</li></ul></li></ul><h2 id="how-do-i-get-it">How do I get it?</h2><ol><li>Install attached Update Set (<em>Notification Helper 2024</em>) </li><li>Navigate to it via URL: /sp_config?id=ndoc&amp;q=<strong><em>QUERY</em></strong><ol><li><strong><em>For example: All Incident notifications that go to the Caller</em></strong>: /sp_config?id=ndoc&amp;q=<strong>collection%3Dincident%5Eactive%3Dtrue%5Erecipient_fieldsLIKEcaller_id</strong></li></ol></li></ol><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/image.png" class="kg-image" alt="Screenshot showing previews of two Out of box notifications from the ndoc page" loading="lazy" width="1970" height="2168" srcset="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w600/2024/07/image.png 600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1000/2024/07/image.png 1000w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/size/w1600/2024/07/image.png 1600w, https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/image.png 1970w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Screenshot showing what the ndoc page looks like</span></figcaption></figure><h2 id="download">Download</h2><div class="kg-card kg-signup-card kg-width-regular " data-lexical-signup-form="" style="background-color: #F0F0F0; display: none;">
            
            <div class="kg-signup-card-content">
                
                <div class="kg-signup-card-text ">
                    <h2 class="kg-signup-card-heading" style="color: #000000;"><span style="white-space: pre-wrap;">Subscribe to gain access to the Update Set.</span></h2>
                    <p class="kg-signup-card-subheading" style="color: #000000;"><span style="white-space: pre-wrap;">Your go-to source for the latest insights, trends, and innovations in the world of ServiceNow</span></p>
                    
        <form class="kg-signup-card-form" data-members-form="signup">
            
            <div class="kg-signup-card-fields">
                <input class="kg-signup-card-input" id="email" data-members-email="" type="email" required="true" placeholder="Your email">
                <button class="kg-signup-card-button kg-style-accent" style="color: #FFFFFF;" type="submit">
                    <span class="kg-signup-card-button-default">Subscribe</span>
                    <span class="kg-signup-card-button-loading"><svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
        <g stroke-linecap="round" stroke-width="2" fill="currentColor" stroke="none" stroke-linejoin="round" class="nc-icon-wrapper">
            <g class="nc-loop-dots-4-24-icon-o">
                <circle cx="4" cy="12" r="3"></circle>
                <circle cx="12" cy="12" r="3"></circle>
                <circle cx="20" cy="12" r="3"></circle>
            </g>
            <style data-cap="butt">
                .nc-loop-dots-4-24-icon-o{--animation-duration:0.8s}
                .nc-loop-dots-4-24-icon-o *{opacity:.4;transform:scale(.75);animation:nc-loop-dots-4-anim var(--animation-duration) infinite}
                .nc-loop-dots-4-24-icon-o :nth-child(1){transform-origin:4px 12px;animation-delay:-.3s;animation-delay:calc(var(--animation-duration)/-2.666)}
                .nc-loop-dots-4-24-icon-o :nth-child(2){transform-origin:12px 12px;animation-delay:-.15s;animation-delay:calc(var(--animation-duration)/-5.333)}
                .nc-loop-dots-4-24-icon-o :nth-child(3){transform-origin:20px 12px}
                @keyframes nc-loop-dots-4-anim{0%,100%{opacity:.4;transform:scale(.75)}50%{opacity:1;transform:scale(1)}}
            </style>
        </g>
    </svg></span>
                </button>
            </div>
            <div class="kg-signup-card-success" style="color: #000000;">
                Email sent! Check your inbox to complete your signup.
            </div>
            <div class="kg-signup-card-error" style="color: #000000;" data-members-error=""></div>
        </form>
        
                    <p class="kg-signup-card-disclaimer" style="color: #000000;"><span style="white-space: pre-wrap;">No spam. Unsubscribe anytime.</span></p>
                </div>
            </div>
        </div><p></p>

<aside data-content-cta class="relative js-toc-ignore">
        <div data-content-cta-blur class="absolute w-full top-0 -translate-y-full bg-gradient-to-b to-70% from-transparent to-white dark:to-gray-900">
            &nbsp; <br> &nbsp; <br> &nbsp;
        </div>

    <div class="flex flex-col items-center text-center w-full px-4 py-10 lg:px-10 text-base bg-accent-10 rounded-md dark:border dark:border-accent-20">
        <h2 class="text-gray-900 dark:text-gray-50 text-xl sm:text-2xl leading-snug tracking-tight">
                    This post is for subscribers only
        </h2>

            <button data-portal="signup" class="bg-accent text-accent-contrast rounded-lg px-3 py-1.5 mt-7 font-semibold text-base leading-relaxed hover:opacity-85">
                Subscribe now
            </button>
            <p class="mt-5 text-base">
                Already have an account? <button data-portal="signin" class="block sm:inline mx-auto font-medium underline decoration-accent decoration-2 underline-offset-2 hover:text-accent">Sign in</button>
            </p>
    </div>
</aside>
 ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ One Click Record Producer Creation ]]></title>
        <description><![CDATA[ Did you know you could create a record producer with a few clicks without having to redefine variables that already exist on a table?

I have a tip that most people don&#39;t know about.

Often times you have an existing data structure that you just want to enable ]]></description>
        <link>https://relay.semaphorepartners.com/articles/one-click-record-producer-creation/</link>
        <guid isPermaLink="false">66a12a517a30c20001b64d67</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Wed, 12 Jun 2024 00:00:00 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/666a233b617f9a433e882183_CleanShot-2024-06-12-at-18.26.31@2x.png" medium="image"/>
        <content:encoded><![CDATA[ <p>Did you know you could create a record producer with a <strong>few clicks</strong> without having to redefine variables that already exist on a table?</p><p>I have a tip that most people don't know about.</p><p>Often times you have an existing data structure that you just want to enable via the Service Catalog/Service Portal. It's pretty time consuming to have to lookup the column names and create all of the variables manually.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/666a237670e2fdf2155dafd8_Related_Links.png" class="kg-image" alt="" loading="lazy" width="464" height="386"></figure><h2 id="here-is-how-you-do-it">Here is how you do it:</h2><ol><li>Visit the table definition via Left Nav. (<em>System Definition &gt;&nbsp;Tables</em>) or if you are viewing the List/Form (<em>Right Click &gt; Configure &gt; Table</em>)</li><li>Scroll Down to "Related Links" &gt; "Add to Service Catalog"</li><li>Select your columns in the "Available" list via the slushbucket</li><li>Give it a Name, Short Description, and Category</li><li>Finally Click "Save and Open"</li><li>You should be redirected to your newly created Record Producer (which includes the variables selected from the slushbucket)</li></ol><p>‍</p><p>‍</p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ The KB-Pocalypse is upon us! ]]></title>
        <description><![CDATA[ On January 1, 2020 a lot of people will wake up with a headache. While some will be due to the previous night&#39;s activities, others will be due to a disappearing Knowledge Base, and a lack of systematic delegation. ]]></description>
        <link>https://relay.semaphorepartners.com/articles/the-kb-pocalypse-is-upon-us/</link>
        <guid isPermaLink="false">66a127e27a30c20001b64d18</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Toby Comer ]]></dc:creator>
        <pubDate>Fri, 12 May 2023 12:14:00 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/645e953ad611b1401366cb0c_istockphoto-533556135-612x612.jpg" medium="image"/>
        <content:encoded><![CDATA[ <p>On January 1, 2020 a lot of people will wake up with a headache. While some will be due to the previous night's activities, others will be due to a disappearing Knowledge Base, and a lack of systematic delegation.</p><h2 id="what-is-kb-pocalypse">What is KB-pocalypse?</h2><p>ServiceNow's default date value has always been set to 2020-01-01. While this was a good 'future' date to use, the future is now here! This means that some records won't be viewed as valid once the new year begins.</p><p>Some examples are:</p><ul><li><strong>Delegates</strong>: Delegate records have a default 'ends' date of 2020-01-01. All delegations with this end date will no longer be active.</li><li><strong>Knowledge Base</strong>: If your knowledge articles have a Valid to (valid_to) date of 2020-01-01, then they won't be visible to anybody searching for them through the Service Portal, or searching/browsing the Knowledge homepage.</li></ul><h2 id="how-can-i-see-if-this-impacts-me">How can I see if this impacts me?</h2><p>Running the script below will show you what tables still have this default value, and how many records still have a date of 2020-01-01.</p><pre><code class="language-js">
/*
This script will find all dictionary entries that are using 2020-01-01 as a default date, and tell you the number of impacted records. 
To actually make changes, you will want to change the update flag to true;
*/

//change this flag to true if you want to update the dictionary entries and affected records
var update = false; 

//Update this value as yyyy-mm-dd
var newDefaultDate = "2030-01-01"; 

var gd = new GlideDate(); 
gd.setValue(newDefaultDate); 
gd.getDisplayValue();

var gdt = new GlideDate(); 
gdt.setValue(newDefaultDate+' 23:59:59'); 
gdt.getDisplayValue();


if(!update){
	gs.print("Since the update flag is false, we are not making upates to these records.");
}

var dictionaryEntry = new GlideRecord("sys_dictionary");
dictionaryEntry.addEncodedQuery("default_valueLIKE2020-01-01^active=true^internal_type=glide_date^ORinternal_type=glide_date_time");
dictionaryEntry.query();
while(dictionaryEntry.next()){
	var table = new GlideRecord(dictionaryEntry.name.toString());
	table.addEncodedQuery(dictionaryEntry.element.toString()+"ON2020-01-01@javascript:gs.dateGenerate('2020-01-01','start')@javascript:gs.dateGenerate('2020-01-01','end')");
	table.query();
	if(table.hasNext()){
		gs.print("There are "+table.getRowCount()+ " "+dictionaryEntry.name.toString()+" records with a "+dictionaryEntry.element.toString()+" date of 2020-01-01");
		if(update){
			var newDate  = dictionaryEntry.internal_type == "glide_date" ? gd.getDisplayValue() : gdt.getDisplayValue();
			while(table.next()){
				table.setValue(dictionaryEntry.element.toString(), newDate);
				table.update();
			}
		}
	} else{
		gs.print("The "+dictionaryEntry.name.toString()+" table still has this default value, but doesn't have any records.");
	}
	
	if(update){
		dictionaryEntry.default_value = dictionaryEntry.default_value.replace(/2020-01-01/gi, newDefaultDate);
		dictionaryEntry.update();
	}
}

</code></pre>
<h2 id="how-can-i-fix-it">How can I fix it?</h2><p>You can fix this by completing the following:</p><ol><li>Change all the affected records as identified above</li><li>Updating the dictionary entry so that future records won't still default to 2020-01-01.</li></ol><p>Please note that the above script will do this for you if you change the update flag to true.</p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Leveraging Now Experience UI Components ]]></title>
        <description><![CDATA[ With the Orlando release of ServiceNow, it&#39;s now possible to create components within Agent Workspace. It seems that per the documentation these components can be used in 3 places:

 1. Agent Workspace Modals
 2. Agent Workspace Landing Pages via UI Builder
 3. Agent Workspace Record views

Though with ]]></description>
        <link>https://relay.semaphorepartners.com/articles/leveraging-now-experience-ui-components/</link>
        <guid isPermaLink="false">66a11da57a30c20001b64cb6</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Fri, 12 May 2023 00:00:00 -0400</pubDate>
        <media:content url="https://storage.ghost.io/c/1d/d2/1dd238c3-5fb7-427a-83b3-ff44c0a590cf/content/images/2024/07/645e9435bd3ac9cf72e9b606_component.png" medium="image"/>
        <content:encoded><![CDATA[ <p>With the Orlando release of ServiceNow, it's now possible to create components within Agent Workspace. It seems that per the <a href="https://docs.servicenow.com/bundle/orlando-servicenow-platform/page/build/components/concept/custom-components.html?ref=relay.semaphorepartners.com">documentation</a> these components can be used in 3 places:</p><ol><li><a href="https://docs.servicenow.com/bundle/orlando-servicenow-platform/page/administer/workspace/task/set-up-ui-action-render-custom-component.html?ref=relay.semaphorepartners.com">Agent Workspace Modals</a></li><li><a href="https://docs.servicenow.com/bundle/orlando-servicenow-platform/page/build/components/task/view-in-uib.html?ref=relay.semaphorepartners.com">Agent Workspace Landing Pages via UI Builder</a></li><li>Agent Workspace Record views</li></ol><p>Though with number 3 the documentation doesn't specifically go into much detail about using components within record views. It specifically says, "You can add custom or standard components to the component area in the Workspace record view." I wasn't able to find a way to create a record view component. I assume this will come in a later release but for the purpose of this guide we are going to use a Modal.</p><p>Recently I ran into an issue with leveraging Dot-Walked journal fields (comments &amp; work notes) on a Catalog task from within Agent Workspace. ServiceNow Support stated this is a known issue with no workaround currently. That left me needing to find an elegant solution to the problem. This was the perfect opportunity to dive into the new UI components released in Orlando.</p><h2 id="create-a-ui-action-register-a-scripting-modal">Create a UI Action &amp; Register a Scripting Modal</h2><p>Follow the <a href="https://docs.servicenow.com/bundle/orlando-servicenow-platform/page/administer/workspace/task/set-up-ui-action-render-custom-component.html?ref=relay.semaphorepartners.com">documentation</a> to create a new registered scripting modal, with a few items to note. We will be leveraging the component for the "Activity stream".</p><ol><li>When creating the scripting modal, use the <em>"now-activity-stream-compose-connected"</em> (instead of the "sn-workspace-header" as listed in the documentation) component for now. We will come back and update it.</li><li>When creating the UI action, be sure to check off "Client" to make it a client side UI action.</li></ol><p>At this point, if you were to click the UI action, nothing would happen. If you notice within the Workspace Client Script, some additional parameters are passed to the component in order to make it work. The only issue is, what parameters are required by the "now-activity-stream-compose-connected" component?</p><pre><code class="language-js">params: {
 primaryValue: 'THIS IS A PRIMARY VALUE',
 secondaryItems:{}
}
</code></pre>
<h3 id="locating-the-component-attributes">Locating the Component Attributes</h3><ol><li>Navigate to the "now-activity-stream-compose-connected" UX component definition page. You should see a related list of "Component Attributes":</li></ol><figure class="kg-card kg-image-card"><img src="https://cdn.prod.website-files.com/6442e543a0b0b67ec3bb9ba0/645e941bbd3ac9cf72e99bac_image-20200529155246954.png" class="kg-image" alt="" loading="lazy" width="1926" height="506"></figure><p>These are exactly what we need to render our component and are actually provided in a handy format. Navigate back to your UI action and update them based on your needs, in my case I will hard code them for now.</p><pre><code class="language-js">params: {
      table: 'sc_req_item',
      isNewRecord: false,
      sysId: '04e276944f131300ef21b4a18110c7b4',
      componentId: 'cid1',
}
</code></pre>
<p>I was feeling pretty good about this, although not documented anywhere, it actually wasn't too hard to find. I tested my UI action... and I got a blank Modal! <strong>What gives?!</strong></p><h3 id="reverse-engineering-a-now-ui-component">Reverse Engineering a NOW UI Component</h3><p>Back on the "UX Component definition" I noticed the "Source Script" field, when I navigated to it, I could see a script link of some minified JavaScript. When I opened it, it wasn't much use even when I unminified it...I poked around in hopes of finding more information, but it was not that helpful. As I was poking around, I noticed some references to a .min.js.map files. Typically source maps are helpful for debugging and returning useful errors to developers. Surely - there must be a way to use the source map to my advantage here.</p><p>After a bit of Googling, I was able to find a GitHub Project "<a href="https://github.com/txase/maximize?ref=relay.semaphorepartners.com">maximize</a>" that sounded promising. Even more promising was a web based version of maximize so I didn't need to install anything. <a href="https://srcbrowse.com/?ref=relay.semaphorepartners.com">SrcBrowse.com</a>. This website became my friend very quickly.</p><ol><li>Navigate to your ServiceNow instance &gt; UX Component definition (from your scripting modal) &gt; Jump to the Source Script record (sys<em>ux</em>lib<em>source</em>script) then click the source script link. This will show you the minified JS.</li><li>Append .map to the end of your URL (.min.js.map), depending on your browser settings, this should download a file.</li><li>Head back to our handy <a href="https://srcbrowse.com/?ref=relay.semaphorepartners.com">source map browser</a> &gt; upload the .map file, then either download the .zip file, or view in browser.</li></ol><p>At this point, we have what we need. After looking around within the files, I think I found what was missing within the nowActivityComboElement.js. ServiceNow actually has some good documentation inline within the code. See below:</p><pre><code class="language-js">/**
* Id for the component.
* @type {string}
*/
componentId,

/**
* Table name of the record that we want to display the activity stream for.
* @type {string}
*/
table,

/**
* Unique identifier for the record.
* @type {string}
*/
sysId,

/**
* Object containing journal fields available. For example:
*
* ```
* {
*   "comments": {
*        name : "comments",
*        label : "Additional comments (Customer Visible)",
*        sys_readonly: false,
*        readonly : false,
*        visible: true,
*        type: 'journal_input',
*      maxLength: 1000
*   },
*   "work_notes": {
*        name : "work_notes",
*        label : "Work Notes",
*        sys_readonly: false,
*        readonly : false,
*        visible: true,
*        type: 'journal_input',
*        maxLength: 2000
*   }
* }
*```
*
* @type {object}
*/
</code></pre>
<h3 id="making-the-component-work">Making the Component Work</h3><p>With the additional parameters defined, I though that maybe the missing piece was the "fields" object as defined above. Head back into your UI action and update the workspace client script code with the additional fields object param:</p><pre><code class="language-js">params: {
    table: 'sc_req_item',
    isNewRecord: false,
    sysId: '04e276944f131300ef21b4a18110c7b4',
    componentId: 'cid1',
    fields: {
        "comments": {
            name: "comments",
            label: "Additional comments (Customer Visible)",
            sys_readonly: false,
            readonly: false,
            visible: true,
            type: 'journal_input',
            maxLength: 1000
            },
            "work_notes": {
                name: "work_notes",
                label: "Work Notes",
                sys_readonly: false,
                readonly: false,
                visible: true,
                type: 'journal_input',
                maxLength: 2000
            }
        }
    }
</code></pre>
<p>At this point our UI action should be functional, with one issue. The modal only shows the "Compose Section".</p><figure class="kg-card kg-image-card"><img src="https://cdn.prod.website-files.com/6442e543a0b0b67ec3bb9ba0/645e941b63e13681db8738e4_image-20200529165624994.png" class="kg-image" alt="" loading="lazy" width="1652" height="772"></figure><p>I was really looking to include the entire Compose and Activity sections in the popup. So what happened? Did we choose the wrong component? Back in our Registered Scripting Modal, the only other "tag" containing activity is "now-activity-stream-connected", perhaps this is the correct component.</p><figure class="kg-card kg-image-card"><img src="https://cdn.prod.website-files.com/6442e543a0b0b67ec3bb9ba0/645e941b4552a534924dcb2b_image-20200529165839521.png" class="kg-image" alt="" loading="lazy" width="1466" height="160"></figure><p>This time if I test my UI action, I only have the Activity stream and not the Compose section:</p><figure class="kg-card kg-image-card"><img src="https://cdn.prod.website-files.com/6442e543a0b0b67ec3bb9ba0/645e941b7c719b839c61f313_image-20200529165947077.png" class="kg-image" alt="" loading="lazy" width="1636" height="464"></figure><p>It seems like this "combo" component that includes both isn't included as a UX component definition.</p><h3 id="creating-a-ux-component-definition">Creating a UX Component Definition</h3><ol><li>Navigate back to the "now-activity-stream-compose-connected" UX Component Definition, it will show as private and non-editable. Choose the Hamburger Menu (or right click) and Choose Insert &amp; Stay.</li><li>Change the "Tag" field to "now-activity-combo" (this was found in our zip file).</li><li>Update your Registered Scripting Modal to leverage this new component:</li></ol><pre><code>Component that combines `now-activity-stream-connected` and now-activity-stream-compose-connected`.
* Basic usage:
*
* ```
* &lt;now-activity-combo
*  component-id="cid1"
*  table="incident"
*  sysId="85071a1347c12200e0ef563dbb9a71c1"
* /&gt;
</code></pre>
<figure class="kg-card kg-image-card"><img src="https://cdn.prod.website-files.com/6442e543a0b0b67ec3bb9ba0/645e941bf13572d6743afc1e_image-20200529170622865.png" class="kg-image" alt="" loading="lazy" width="1722" height="886"></figure><p>We are now leveraging the component from the source files that we found.</p><h3 id="a-few-notes">A few notes</h3><ul><li>This was done with an Orlando Instance.</li><li>This is likely <em>NOT</em> supported by ServiceNow. Please use carefully!</li><li>ServiceNow may add this "now-activity-combo" component in the future and this workaround may not be needed, or they may fix the bug in Workspace so you can dot-walk to journal fields.</li></ul><h3 id="wrap-up">Wrap Up</h3><p>I hope this was a helpful deep dive into the new UI framework that ServiceNow is laying the groundwork for. I am very excited to use these components and excited to build additional custom components. I hope that others are able to leverage this and perhaps use other existing components as they see fit!</p> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ ServiceNow and Siri Shortcuts app ]]></title>
        <description><![CDATA[ As a huge Apple fan, I eagerly awaited the launch of iOS 12. With iOS 12, Apple has launched the Siri Shortcuts app which allows anyone to create a “Shortcut”: a series of steps that can interact with third party applications, the iOS device, and Siri.

As soon as I ]]></description>
        <link>https://relay.semaphorepartners.com/articles/servicenow-and-siri-shortcuts-app-2/</link>
        <guid isPermaLink="false">66a129d37a30c20001b64d57</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Fri, 21 Sep 2018 00:00:00 -0400</pubDate>
        <media:content url="https://images.unsplash.com/photo-1592925455492-94fb2d55e2a5?crop&#x3D;entropy&amp;cs&#x3D;tinysrgb&amp;fit&#x3D;max&amp;fm&#x3D;jpg&amp;ixid&#x3D;M3wxMTc3M3wwfDF8c2VhcmNofDR8fHNpcml8ZW58MHx8fHwxNzIxODM4MTE3fDA&amp;ixlib&#x3D;rb-4.0.3&amp;q&#x3D;80&amp;w&#x3D;2000" medium="image"/>
        <content:encoded><![CDATA[ <p>As a <strong>huge</strong> Apple fan, I eagerly awaited the launch of iOS 12. With iOS 12, Apple has launched the Siri Shortcuts app which allows anyone to create a “<em>Shortcut</em>”: a series of steps that can interact with third party applications, the iOS device, and Siri.</p><figure class="kg-card kg-image-card"><img src="https://miro.medium.com/v2/resize:fit:490/1*JWRr1cGeNXvqEJdsbQVE4w.png" class="kg-image" alt="" loading="lazy" width="245" height="492"></figure><p>As soon as I got my hands on the app, I saw quite a number of similarities between Shortcuts and ServiceNow’s Flow Designer. Steps within Shortcuts can be sequenced and they have inputs and outputs (“Magic Variables”) similar to “Data Pills” within Flow Designer.</p><figure class="kg-card kg-image-card"><img src="https://miro.medium.com/v2/resize:fit:1084/1*-xWiF_bWrnqnqoQyr4yc_w.png" class="kg-image" alt="" loading="lazy" width="542" height="590"></figure><p>The Shortcuts app has a number of easy to use “Actions” which immediately reminded me of Flow Designer’s “Spokes”. In the Shortcuts app, many “developer” or “advanced” user Actions exist. After seeing some advanced actions such as “Get Contents of URL” and “Get Dictionary from Input” I wondered, can I link a Shortcut to ServiceNow?</p><figure class="kg-card kg-image-card"><img src="https://miro.medium.com/v2/resize:fit:490/1*s7uLZPYXr9rtaLeOcotlqQ.png" class="kg-image" alt="" loading="lazy" width="245" height="492"></figure><p>Within a short amount of time I was able to create a pretty useful Shortcut. The Shortcut creates a ServiceNow Incident using Siri dictation. It then reads back the Incident number it created via the table REST API. This is obviously a quick proof of concept, but it’s clear that the possibilities are endless. With no code, you could grab native device data such as Location or System IP address, pass that data to a REST API, and parse the results back to spoken text.</p><p>Some areas for improvement include using an API key rather than a username/password variable or even using the new Chat framework that ServiceNow has released with London.</p><p>For anyone interested, <a href="https://www.icloud.com/shortcuts/5c0d47f60fde4cc196d6f04028a3a277?ref=relay.semaphorepartners.com" rel="noopener ugc nofollow">here</a> is the Shortcut. All you need to do is download the iOS application “Shortcuts” and install this. Once installed, add in your instance, username, and password.</p><p>I am really excited to see what clever ideas the community comes up with here. Give it a try!</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://support.apple.com/guide/shortcuts/welcome/ios?source=post_page-----4f8a843eb01c--------------------------------"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Shortcuts User Guide</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://support.apple.com/favicon.ico" alt=""><span class="kg-bookmark-author">Apple Support</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://help.apple.com/assets/645D5D228BE0233D28263F4B/645D5D258BE0233D28263F5A/en_US/d230a25cb974f8908871af04caad89a1.png" alt=""></div></a></figure> ]]></content:encoded>
    </item>
    <item>
        <title><![CDATA[ Scoped APIs are not Global enough ]]></title>
        <description><![CDATA[ Scoped apps were first introduced in the Fuji release. This was a really exciting time for ServiceNow partners, developers, and the community. For partners, an app store within the ServiceNow ecosystem was a new business model. For developers and admins, there was finally a way to get away from update ]]></description>
        <link>https://relay.semaphorepartners.com/articles/scoped-apis-are-not-global-enough/</link>
        <guid isPermaLink="false">66a128e07a30c20001b64d41</guid>
        <category><![CDATA[  ]]></category>
        <dc:creator><![CDATA[ Jonathan Jacob ]]></dc:creator>
        <pubDate>Thu, 14 Jun 2018 12:18:00 -0400</pubDate>
        <media:content url="https://images.unsplash.com/photo-1476304884326-cd2c88572c5f?crop&#x3D;entropy&amp;cs&#x3D;tinysrgb&amp;fit&#x3D;max&amp;fm&#x3D;jpg&amp;ixid&#x3D;M3wxMTc3M3wwfDF8c2VhcmNofDM4fHxnbG9iYWwlMjBjb2RlfGVufDB8fHx8MTcyMTgzNzkyMXww&amp;ixlib&#x3D;rb-4.0.3&amp;q&#x3D;80&amp;w&#x3D;2000" medium="image"/>
        <content:encoded><![CDATA[ <p><a href="https://community.servicenow.com/community?id=community_blog&sys_id=addca665dbd0dbc01dcaf3231f96192e&ref=relay.semaphorepartners.com">Scoped apps</a> were first introduced in the Fuji release. This was a really exciting time for ServiceNow partners, developers, and the community. For partners, an app store within the ServiceNow ecosystem was a new business model. For developers and admins, there was finally a way to get away from update sets and there were lots of new features to play with. For just about everyone, there was a new way to encapsulate or “sandbox” business logic and data. This idea was and still is welcomed by me; I enjoy creating scoped apps and encourage my customers, and colleagues to embrace them.</p><p>For those that don’t remember the early days of scoped apps, we were all a bit shocked and surprised at how limited the <a href="https://community.servicenow.com/community?id=community_blog&sys_id=abfde62ddbd0dbc01dcaf3231f961922&ref=relay.semaphorepartners.com">APIs</a> were. As time progressed, ServiceNow began exposing more and more “Scoped APIs” and today, I would say most if not all of the necessary APIs are exposed in the “Scoped” world. To get to that point, it took a great deal of feedback from the ServiceNow community to explain to the development team <em>why</em> some of these APIs are crucial to build amazing applications on the ServiceNow platform.</p><p>More recently, I see that we are faced with the opposite issue. Some scoped APIs do not function within the global scope. The frustrating piece is, it seems to be at random which APIs work and which don’t. If you regularly explore the developer site looking over the API documentation (as I do) you may have seen this disclaimer:</p><p>“**Please note: **The APIs below are intended for scoped applications and may behave differently in the global scope.”</p><p>Within the platform, specifically in the Global scope, I should be able to use APIs that are available to a scoped app or a similar equivalent API. I know that scoped apps are the future and that we should all be adopting them. With a few caveats I agree with this but that is not for this post to discuss. It seems as more Scoped API use cases have emerged, ServiceNow has built some amazing APIs for developers to use, however some of them were not ported into the global scope.</p><p>Consider the following example:</p><pre><code class="language-js">var att = new GlideSysAttachment().getContentStream(attachment);
var xmlDoc = XMLDocument2(att);gs.info(xmlDoc.toString());
</code></pre>
<p>This code executes properly is a scoped app, <em>however in the global scope, it does not</em>. It produces the following error:</p><pre><code class="language-js">Evaluator: org.mozilla.javascript.EcmaError: Method "XMLDocument2" called on incompatible object.
   Caused by error in script at line 2
</code></pre>
<p>This leads me to believe the correct method signatures were not setup in the global scope. Because the XMLDocument2 API, works in other contexts:</p><pre><code class="language-js">//Example from developer.servicenow.com
var xmlString = "&lt;test&gt;" +
                "  &lt;one&gt;" +
                "    &lt;two att=\"xxx\"&gt;abcd1234&lt;/two&gt;" +
                "    &lt;three boo=\"yah\" att=\"yyy\"&gt;1234abcd&lt;/three&gt;" +
                "    &lt;two&gt;another&lt;/two&gt;" +
                "  &lt;/one&gt;" +
                "  &lt;number&gt;1234&lt;/number&gt;" +
                "&lt;/test&gt;";
var xmlDoc = new XMLDocument2();
xmlDoc.parseXML(xmlString);
gs.info(xmlDoc.toString());
</code></pre>
<p>This is just one example, I am sure other similar examples exist. My hope here is that while developing Scoped APIs, ServiceNow still considers that some use cases need to exist in the Global scope. Thus, Global APIs should have all of the APIs that exist in the Scoped world or at least a similar equivalent.</p><hr><p>I share this feedback in hopes to better ServiceNow as a platform for all use cases.</p><p>Originally posted on <a href="https://medium.com/@jonnyfive/scoped-apis-are-not-global-enough-aa21ff161b0c?ref=relay.semaphorepartners.com">Medium</a></p> ]]></content:encoded>
    </item>

</channel>
</rss>
