SandilyaSridhar
ServiceNow Employee

 

Implementation Playbook

How to Programmatically Toggle the Now Assist Chatbot in Employee Center

Add an open/close toggle directly in the EC header — using the platform's own internal event lifecycle. No OOB code modified.

Before you start: Apply all changes in a custom Update Set. Test in sub-production first. Changes go in the Employee Center Header (sp_header_footer).
1

How It Works

The toggle reuses the same internal mechanisms as the native FAB sparkle button (open) and chevron-down close button (close). No OOB code is modified.

Open Sequence

1
Unhide Wrapper
Set c.hideChat = false on the Dialog Widget's scope
2
Wait 300ms
FAB sparkle button needs time to render
3
Click FAB
Triggers CREATE_WINDOW + OPEN_WINDOW

Close Sequence

1
Find Close Button
Traverse shadow DOM to #close-window
2
Click It
Triggers CLOSE_WINDOW lifecycle
3
Event Fires
NOW_ASSIST_DIALOG#CLOSED syncs state
2

Events & DOM Structure

Events Reference

Event Direction Description
NOW_ASSIST_DIALOG#OPENED Output Fired when chat opens
NOW_ASSIST_DIALOG#CLOSED Output Fired when chat closes
NOW_ASSIST_DIALOG#PINNED Output Fired when chat is pinned/unpinned
SN_WINDOW#WINDOW_OPENED Internal Dispatched to content element on open
SN_WINDOW#WINDOW_CLOSED Internal Dispatched to content element on close
SN_WINDOW_MANAGER#OPEN_WINDOW Action Internal open action
SN_WINDOW_MANAGER#CLOSE_WINDOW Action Internal close action

Shadow DOM Hierarchy

now-assist-full-page-wrapper-app └─ shadowRoot └─ now-assist-dialog [opened] └─ shadowRoot ├─ sn-window-manager │ └─ shadowRoot │ └─ sn-window │ └─ shadowRoot │ └─ now-button#close-window CLOSE │ └─ shadowRoot → button └─ sn-nass-sp-sparkle-icon FAB └─ shadowRoot └─ now-button-circular └─ shadowRoot → button OPEN

Angular Wrapper

#sn-na-dw-wrapper is controlled by the Dialog Widget's controller. c.hideChat must be false before the FAB is clickable.

Wrapper HTML
 
<div class="chat-wrapper" id="sn-na-dw-wrapper"
     ng-class="{'hidden': c.hideChat, 'chat-pinned': !c.hideResizer}">
3

Console Testing Scripts

Paste into Chrome DevTools to validate before applying widget changes.

3.1 — Open

Console — Open
 
function openNowAssist() {
    // Unhide wrapper via Angular scope
    var wrapper = document.getElementById('sn-na-dw-wrapper');
    var s = angular.element(wrapper).scope();
    while (s && !(s.c && s.c.hasOwnProperty('hideChat'))) {
        s = s.$parent;
    }
    s.c.hideChat = false;
    if (!s.$$phase && !s.$root.$$phase) s.$apply();

    // Wait for FAB, then click
    setTimeout(function() {
        var app = document.querySelector('now-assist-full-page-wrapper-app');
        var dialog = app.shadowRoot.querySelector('now-assist-dialog');
        var sparkle = dialog.shadowRoot.querySelector('sn-nass-sp-sparkle-icon');
        var circular = sparkle.shadowRoot.querySelector('now-button-circular');
        circular.shadowRoot.querySelector('button').click();
        console.log('✓ Now Assist OPENED');
    }, 300);
}
openNowAssist();

3.2 — Close

Console — Close
 
function closeNowAssist() {
    var app = document.querySelector('now-assist-full-page-wrapper-app');
    var dialog = app.shadowRoot.querySelector('now-assist-dialog');
    var winMgr = dialog.shadowRoot.querySelector('sn-window-manager');
    var snWin = winMgr.shadowRoot.querySelector('sn-window');
    var closeBtn = snWin.shadowRoot.querySelector('now-button#close-window');
    closeBtn.shadowRoot.querySelector('button').click();
    console.log('✓ Now Assist CLOSED');
}
closeNowAssist();

3.3 — Diagnostic

Console — Diagnostic
 
var app = document.querySelector('now-assist-full-page-wrapper-app');
console.log('1. app:', !!app);
var dialog = app?.shadowRoot?.querySelector('now-assist-dialog');
console.log('2. dialog:', !!dialog, 'opened:', dialog?.hasAttribute('opened'));
var sparkle = dialog?.shadowRoot?.querySelector('sn-nass-sp-sparkle-icon');
console.log('3. sparkle:', !!sparkle);
var circular = sparkle?.shadowRoot?.querySelector('now-button-circular');
console.log('4. FAB btn:', !!circular?.shadowRoot?.querySelector('button'));
var wrapper = document.getElementById('sn-na-dw-wrapper');
var s = angular.element(wrapper).scope();
while (s && !(s.c?.hasOwnProperty('hideChat'))) { s = s.$parent; }
console.log('5. scope:', !!s, 'hideChat:', s?.c?.hideChat);
4

Widget Implementation

Three changes to the Employee Center Header (sp_header_footer).

Button is gated behind ng-if="data.showSparkle" — only renders when Now Assist is enabled on the portal.

4.1 — Client Script

Add after hideSearchWidgetForNASS, before c.evaluateExperienceFeedbackDrawerVisibility:

Client Script
 
/* ── Now Assist Header Toggle ── */
c.nowAssistOpen = false;

function getDialogScope() {
    var wrapper = document.getElementById('sn-na-dw-wrapper');
    if (!wrapper) return null;
    var s = angular.element(wrapper).scope();
    while (s && !(s.c && s.c.hasOwnProperty('hideChat'))) {
        s = s.$parent;
    }
    return s;
}

function safeApply(scope) {
    if (!scope.$$phase && !scope.$root.$$phase) scope.$apply();
}

function clickFab() {
    var app = document.querySelector('now-assist-full-page-wrapper-app');
    if (!app || !app.shadowRoot) return false;
    var dialog = app.shadowRoot.querySelector('now-assist-dialog');
    if (!dialog || !dialog.shadowRoot) return false;
    var sparkle = dialog.shadowRoot.querySelector('sn-nass-sp-sparkle-icon');
    if (!sparkle || !sparkle.shadowRoot) return false;
    var circular = sparkle.shadowRoot.querySelector('now-button-circular');
    if (!circular || !circular.shadowRoot) return false;
    var btn = circular.shadowRoot.querySelector('button');
    if (btn) { btn.click(); return true; }
    return false;
}

function clickCloseButton() {
    var app = document.querySelector('now-assist-full-page-wrapper-app');
    if (!app || !app.shadowRoot) return false;
    var dialog = app.shadowRoot.querySelector('now-assist-dialog');
    if (!dialog || !dialog.shadowRoot) return false;
    var winMgr = dialog.shadowRoot.querySelector('sn-window-manager');
    if (!winMgr || !winMgr.shadowRoot) return false;
    var snWin = winMgr.shadowRoot.querySelector('sn-window');
    if (!snWin || !snWin.shadowRoot) return false;
    var closeBtn = snWin.shadowRoot.querySelector('now-button#close-window');
    if (!closeBtn || !closeBtn.shadowRoot) return false;
    var btn = closeBtn.shadowRoot.querySelector('button');
    if (btn) { btn.click(); return true; }
    return false;
}

c.toggleNowAssist = function() {
    var dlgScope = getDialogScope();
    if (!dlgScope) return;

    if (c.nowAssistOpen) {
        clickCloseButton();
        c.nowAssistOpen = false;
    } else {
        dlgScope.c.hideChat = false;
        safeApply(dlgScope);
        $timeout(function() {
            clickFab();
            c.nowAssistOpen = true;
        }, 300);
    }
};

c._syncNowAssistState = function() {
    var app = document.querySelector('now-assist-full-page-wrapper-app');
    if (!app) return;
    app.addEventListener('NOW_ASSIST_DIALOG#OPENED', function() {
        c.nowAssistOpen = true;
        try { safeApply($scope); } catch (e) {}
    });
    app.addEventListener('NOW_ASSIST_DIALOG#CLOSED', function() {
        c.nowAssistOpen = false;
        try { safeApply($scope); } catch (e) {}
    });
};
$timeout(c._syncNowAssistState, 2000);
/* ── End Now Assist Header Toggle ── */

4.2 — Template (HTML)

Add before <!-- Esc Notifications Bell --> inside the desktop header-menu div:

Template
 
<!-- Now Assist Toggle -->
<div class="gt-menu-item na-header-toggle"
     ng-if="data.showSparkle">
  <a href="javascript&colon;void(0)"
     id="na-header-toggle-btn"
     ng-click="c.toggleNowAssist()"
     aria-label="{{c.nowAssistOpen
       ? 'Close Now Assist' : 'Open Now Assist'}}"
     data-toggle="tooltip"
     data-placement="bottom"
     data-original-title="{{c.nowAssistOpen
       ? 'Close Now Assist' : 'Open Now Assist'}}">
    <span class="na-toggle-icon" aria-hidden="true">
      <svg xmlns="http://www.w3.org/2000/svg"
           viewBox="0 0 24 24" width="16"
           height="16" fill="currentColor">
        <path d="M12.87 3.52c.2-.77 1.28-.77
          1.48 0l.5 1.9a3.3 3.3 0 0 0 2.33
          2.33l1.9.5c.77.2.77 1.28 0 1.48l-1.9
          .5a3.3 3.3 0 0 0-2.33 2.34l-.5
          1.89c-.2.77-1.28.77-1.48 0l-.5-1.9a3.3
          3.3 0 0 0-2.33-2.33l-1.9-.5c-.77-.2
          -.77-1.28 0-1.48l1.9-.5a3.3 3.3 0
          0 0 2.33-2.33l.5-1.9z"/>
      </svg>
    </span>
    <span ng-bind-html="'${Now Assist}'"
          aria-hidden="true"></span>
  </a>
</div>

4.3 — CSS (SCSS)

CSS
 
/* ── Now Assist Header Toggle ── */
.na-header-toggle {
  a {
    display: flex;
    align-items: center;
    gap: 6px;
  }
}
.na-toggle-icon {
  display: inline-flex;
  align-items: center;
  svg { fill: $navbar-inverse-link-color; }
}
.na-header-toggle a:hover .na-toggle-icon svg {
  fill: $navbar-inverse-link-hover-color;
}
/* ── End Now Assist Header Toggle ── */
5

Why This Is Safe

  • No OOB code modified — Dialog Widget, sn-window-manager, and all Now Experience components untouched.
  • Same code paths — Open uses FAB's native click. Close uses #close-window's native click.
  • Gated visibility — Only renders when Now Assist is enabled (ng-if="data.showSparkle").
  • Safe AngularsafeApply checks $$phase before $apply.
  • Upgrade resilient — If shadow DOM changes, toggle returns false without errors.
6

Troubleshooting

Symptom Resolution
Button not visible Verify data.showSparkle is true.
First click unresponsive Window not yet created. FAB click handles it. Increase $timeout to 500 if needed.
Open does nothing Run diagnostic (3.3). Verify each shadow DOM node is non-null.
Close does nothing #close-window only exists when chat is open.
$rootScope:inprog safeApply prevents this. Use exact code from 4.1.
nowWindowManager undefined Expected in Service Portal. Code doesn't depend on it.
Developed through iterative DOM analysis, event tracing, and sn-window-manager source review. All paths validated on a live instance.

Now Assist Self-Service · Employee Center · Service Portal

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