ui-builder-patterns について
ServiceNow UI Builder で Next Experience のワークスペースやアプリケーションを構築するスキルです。ページ構造、データブローカー、クライアント状態、イベント、コンポーネント、再利用可能なマクロポーネントと、ページ作成・変更ツールを説明します。
主な機能
- 01ページとバリアントの構造
- 02GraphQL、Script、REST、Transform のデータブローカー
- 03クライアント状態パラメーター
- 04コンポーネントのイベントとハンドラー
- 05再利用可能なマクロポーネント
- 06ワークスペースとページの作成ツール
活用シーン
- 01インシデント一覧とフォームのページ構築
- 02クライアント状態とイベントによるコンポーネント連携
- 03再利用可能な UI 部品の作成
- 04ページデータ用データブローカーの設定
MCP 対応
ページ作成、要素追加、データブローカー・クライアント状態・イベントの設定、ワークスペース構築、ページ更新に使う MCP ツールを説明しています。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ serac-labs/ serac --skill ui-builder-patternsインストール後、Claude Code で/ui-builder-patternsを入力して読み込まれたか確認します。
詳細
ServiceNow の UI Builder パターン
UI Builder(UIB)は、Next Experience のワークスペースやアプリケーションを構築するための ServiceNow の最新フレームワークです。
UI Builder のアーキテクチャ
コンポーネントの階層
UX Application
└── App Shell
└── Chrome (Header, Navigation)
└── Pages
└── Variants
└── Macroponents
└── Components
└── Elements
主な概念
| 概念 | 説明 |
|---|---|
| マクロポーネント | コンポーネントとロジックを含む再利用可能なコンテナ |
| コンポーネント | UI の構成要素(リスト、フォーム、ボタン) |
| データブローカー | コンポーネントのデータを取得、管理します |
| クライアント状態 | ページレベルの状態管理 |
| イベント | コンポーネント間の通信 |
ページ構造
ページの構成
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)
データブローカー
データブローカーの種類
| 種類 | 用途 | 例 |
|---|---|---|
| GraphQL | テーブルクエリ | インシデントリスト |
| Script | 複雑なロジック | 計算済みメトリクス |
| REST | 外部 API | 天気データ |
| Transform | データ操作 | 日付の形式設定 |
GraphQL データブローカー
// 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 データブローカー(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)
クライアント状態パラメーター
クライアント状態の定義
// 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": []
}
}
コンポーネントでのクライアント状態の使用
// 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"
}
}
]
}
イベントとハンドラー
イベントの種類
| イベント | トリガー | ペイロード |
|---|---|---|
NOW_RECORD_LIST#RECORD_SELECTED | 行のクリック | { sys_id, table } |
NOW_BUTTON#CLICKED | ボタンのクリック | { label } |
NOW_DROPDOWN#SELECTED | ドロップダウンの変更 | { value } |
CUSTOM#EVENT_NAME | カスタムイベント | カスタムペイロード |
イベントハンドラーの設定
// 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"
}
}
]
}
クライアントスクリプトのイベントハンドラー(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)
コンポーネントの設定
一般的なコンポーネント
| コンポーネント | 目的 | 主なプロパティ |
|---|---|---|
now-record-list | データテーブル | columns, query, table |
now-record-form | レコードフォーム | table, sysId, fields |
now-button | アクションボタン | label, variant, icon |
now-card | カードコンテナ | header, content |
now-tabs | タブコンテナ | tabs, activeTab |
now-modal | モーダルダイアログ | opened, title |
レコードリストの設定
{
"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"
}
}
フォームの設定
{
"component": "now-record-form",
"properties": {
"table": "incident",
"sysId": "@state.selectedIncident",
"fields": ["short_description", "description", "priority", "assignment_group", "assigned_to"],
"readOnly": false
}
}
マクロポーネント
再利用可能なマクロポーネントの作成
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)
マクロポーネントのプロパティ
{
"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 ツールの統合
使用可能な UIB ツール
| ツール | 目的 |
|---|---|
snow_uib_page_manage (action='create') | 新しいページを作成 |
snow_uib_page_manage (action='add_element') | ページにコンポーネントを追加 |
snow_create_uib_data_broker | データブローカーを作成 |
snow_create_uib_client_state | クライアント状態を定義 |
snow_create_uib_event | イベントを設定 |
snow_create_complete_workspace | ワークスペース全体を作成 |
snow_record_manage (action='update') | ページを変更 |
ワークフローの例
// 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_uib_page_manage({
action: "add_element",
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,
})
ベストプラクティス
- データブローカーを使用する - コンポーネント内でデータを直接取得しないでください
- UI にはクライアント状態を使用する - フィルター、選択、表示モードに使用します
- 通信にはイベントを使用する - イベントを介してコンポーネントを疎結合にします
- 再利用にはマクロポーネントを使用する - 再利用可能な構成要素を作成します
- クエリには GraphQL を使用する - Script ブローカーより効率的です
- 構造を確認する - デプロイ前にページレイアウトとバインディングを手動で確認します
- モバイル用のバリアント - レスポンシブなバリアントを作成します
- アクセシビリティ - WCAG ガイドラインに従います
コメント
Markdown 対応