ui-builder-patterns
39
总安装量
10
周安装量
#9738
全站排名
安装命令
npx skills add https://github.com/groeimetai/snow-flow --skill ui-builder-patterns
Agent 安装分布
claude-code
8
antigravity
7
gemini-cli
6
windsurf
6
codex
6
opencode
6
Skill 文档
UI Builder Patterns for ServiceNow
UI Builder (UIB) is ServiceNow’s modern framework for building Next Experience workspaces and applications.
UI Builder Architecture
Component Hierarchy
UX Application
âââ App Shell
âââ Chrome (Header, Navigation)
âââ Pages
âââ Variants
âââ Macroponents
âââ Components
âââ Elements
Key Concepts
| Concept | Description |
|---|---|
| Macroponent | Reusable container with components and logic |
| Component | UI building block (list, form, button) |
| Data Broker | Fetches and manages data for components |
| Client State | Page-level state management |
| Event | Communication between components |
Page Structure
Page Anatomy
Page: incident_list
âââ Variants
â âââ Default (desktop)
â âââ Mobile
âââ Data Brokers
â âââ incident_data (GraphQL)
â âââ user_preferences (Script)
âââ Client States
â âââ selectedRecord
â âââ filterActive
âââ Events
â âââ RECORD_SELECTED
â âââ FILTER_APPLIED
âââ Layout
âââ Header (macroponent)
âââ Sidebar (macroponent)
âââ Content (macroponent)
Data Brokers
Types of Data Brokers
| Type | Use Case | Example |
|---|---|---|
| GraphQL | Table queries | Incident list |
| Script | Complex logic | Calculated metrics |
| REST | External APIs | Weather data |
| Transform | Data manipulation | Format dates |
GraphQL Data Broker
// Data Broker: incident_list
// Type: GraphQL
// Query
query ($limit: Int, $query: String) {
GlideRecord_Query {
incident(
queryConditions: $query
limit: $limit
) {
number { value displayValue }
short_description { value }
priority { value displayValue }
state { value displayValue }
assigned_to { value displayValue }
sys_id { value }
}
}
}
// Variables (from client state or props)
{
"limit": 50,
"query": "active=true"
}
Script Data Broker (ES5)
// Data Broker: incident_metrics
// Type: Script
(function execute(inputs, outputs) {
var result = {
total: 0,
byPriority: {},
avgAge: 0
};
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.query();
var totalAge = 0;
while (gr.next()) {
result.total++;
// Count by priority
var priority = gr.getValue('priority');
if (!result.byPriority[priority]) {
result.byPriority[priority] = 0;
}
result.byPriority[priority]++;
// Calculate age
var opened = new GlideDateTime(gr.getValue('opened_at'));
var now = new GlideDateTime();
var age = gs.dateDiff(opened, now, true);
totalAge += parseInt(age);
}
if (result.total > 0) {
result.avgAge = Math.round(totalAge / result.total / 3600); // hours
}
outputs.metrics = result;
})(inputs, outputs);
Client State Parameters
Defining Client State
// Page Client State Parameters
{
"selectedIncident": {
"type": "string",
"default": ""
},
"filterQuery": {
"type": "string",
"default": "active=true"
},
"viewMode": {
"type": "string",
"default": "list",
"enum": ["list", "card", "split"]
},
"selectedRecords": {
"type": "array",
"items": { "type": "string" },
"default": []
}
}
Using Client State in Components
// In component configuration
{
"query": "@state.filterQuery",
"selectedItem": "@state.selectedIncident"
}
// Updating client state via event
{
"eventName": "NOW_RECORD_LIST#RECORD_SELECTED",
"handlers": [
{
"action": "UPDATE_CLIENT_STATE",
"payload": {
"selectedIncident": "@payload.sys_id"
}
}
]
}
Events and Handlers
Event Types
| Event | Trigger | Payload |
|---|---|---|
NOW_RECORD_LIST#RECORD_SELECTED |
Row click | { sys_id, table } |
NOW_BUTTON#CLICKED |
Button click | { label } |
NOW_DROPDOWN#SELECTED |
Dropdown change | { value } |
CUSTOM#EVENT_NAME |
Custom event | Custom payload |
Event Handler Configuration
// Event: Record Selected
{
"eventName": "NOW_RECORD_LIST#RECORD_SELECTED",
"handlers": [
{
"action": "UPDATE_CLIENT_STATE",
"payload": {
"selectedIncident": "@payload.sys_id"
}
},
{
"action": "REFRESH_DATA_BROKER",
"payload": {
"dataBrokerId": "incident_details"
}
},
{
"action": "DISPATCH_EVENT",
"payload": {
"eventName": "INCIDENT_SELECTED",
"payload": "@payload"
}
}
]
}
Client Script Event Handler (ES5)
// Client Script for custom event handling
(function(coeffects) {
var dispatch = coeffects.dispatch;
var state = coeffects.state;
var payload = coeffects.action.payload;
// Custom logic
var selectedId = payload.sys_id;
// Update multiple states
dispatch('UPDATE_CLIENT_STATE', {
selectedIncident: selectedId,
detailsVisible: true
});
// Conditional dispatch
if (payload.priority === '1') {
dispatch('DISPATCH_EVENT', {
eventName: 'CRITICAL_INCIDENT_SELECTED',
payload: payload
});
}
})(coeffects);
Component Configuration
Common Components
| Component | Purpose | Key Properties |
|---|---|---|
now-record-list |
Data table | columns, query, table |
now-record-form |
Record form | table, sysId, fields |
now-button |
Action button | label, variant, icon |
now-card |
Card container | header, content |
now-tabs |
Tab container | tabs, activeTab |
now-modal |
Modal dialog | opened, title |
Record List Configuration
{
"component": "now-record-list",
"properties": {
"table": "incident",
"query": "@state.filterQuery",
"columns": [
{ "field": "number", "label": "Number" },
{ "field": "short_description", "label": "Description" },
{ "field": "priority", "label": "Priority" },
{ "field": "state", "label": "State" },
{ "field": "assigned_to", "label": "Assigned To" }
],
"pageSize": 20,
"selectable": true,
"selectedRecords": "@state.selectedRecords"
}
}
Form Configuration
{
"component": "now-record-form",
"properties": {
"table": "incident",
"sysId": "@state.selectedIncident",
"fields": [
"short_description",
"description",
"priority",
"assignment_group",
"assigned_to"
],
"readOnly": false
}
}
Macroponents
Creating Reusable Macroponents
Macroponent: incident-summary-card
âââ Properties (inputs)
â âââ incidentSysId (string)
â âââ showActions (boolean)
âââ Internal State
â âââ expanded (boolean)
âââ Data Broker
â âââ incident_data (uses incidentSysId)
âââ Layout
âââ now-card
â âââ Header: @data.incident.number
â âââ Content: @data.incident.short_description
â âââ Footer: Action buttons
âââ now-modal (if expanded)
Macroponent Properties
{
"properties": {
"incidentSysId": {
"type": "string",
"required": true,
"description": "Sys ID of incident to display"
},
"showActions": {
"type": "boolean",
"default": true,
"description": "Show action buttons"
},
"variant": {
"type": "string",
"default": "default",
"enum": ["default", "compact", "detailed"]
}
}
}
MCP Tool Integration
Available UIB Tools
| Tool | Purpose |
|---|---|
snow_create_uib_page |
Create new page |
snow_create_uib_component |
Add component to page |
snow_create_uib_data_broker |
Create data broker |
snow_create_uib_client_state |
Define client state |
snow_create_uib_event |
Configure events |
snow_create_complete_workspace |
Full workspace |
snow_update_uib_page |
Modify page |
snow_validate_uib_page_structure |
Validate structure |
Example Workflow
// 1. Create workspace
await snow_create_complete_workspace({
name: 'IT Support Workspace',
description: 'Agent workspace for IT support',
landing_page: 'incident_list'
});
// 2. Create data broker
await snow_create_uib_data_broker({
page_id: pageId,
name: 'incident_list',
type: 'graphql',
query: incidentQuery
});
// 3. Add components
await snow_create_uib_component({
page_id: pageId,
component: 'now-record-list',
properties: listConfig
});
// 4. Configure events
await snow_create_uib_event({
page_id: pageId,
event_name: 'NOW_RECORD_LIST#RECORD_SELECTED',
handlers: eventHandlers
});
Best Practices
- Use Data Brokers – Never fetch data directly in components
- Client State for UI – Use for filters, selections, view modes
- Events for Communication – Decouple components via events
- Macroponents for Reuse – Create reusable building blocks
- GraphQL for Queries – More efficient than Script brokers
- Validate Structure – Use validation tools before deployment
- Mobile Variants – Create responsive variants
- Accessibility – Follow WCAG guidelines