mobile-development について
ServiceNow のモバイル開発向けスキルです。アプリ、画面、カード、通知、オフラインルール、アクションの設定方法を、ServiceNow レコードを使ったモバイル体験の ES5 例とともに示します。
主な機能
- 01モバイルアプリと画面の設定
- 02リストカードの設定とアクション
- 03プッシュ通知の設定
- 04オフライン同期ルールとキュー処理
- 05モバイル専用アクション
活用シーン
- 01IT サポート用モバイルアプリの設定
- 02モバイルリストでのインシデント表示
- 03割り当て済みインシデントの通知
- 04オフラインデータを使った作業支援
MCP 対応
モバイル設定の照会、モバイルスクリプトのテスト、設定アーティファクトの検索に使う MCP ツールを紹介します。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ serac-labs/ serac --skill mobile-developmentインストール後、Claude Code で/mobile-developmentを入力して読み込まれたか確認します。
詳細
ServiceNow のモバイル開発
モバイル開発では、オフライン機能を備えたネイティブのモバイルエクスペリエンスを実現できます。
モバイルアーキテクチャ
Mobile App Configuration
├── App Screens
│ ├── List Views
│ ├── Detail Views
│ └── Card Builders
├── Push Notifications
├── Offline Rules
└── Mobile Actions
主なテーブル
| テーブル | 用途 |
|---|---|
sys_sg_mobile_app | モバイルアプリの設定 |
sys_sg_screen | モバイル画面 |
sys_sg_card_builder | カードビルダー |
sys_push_notification | プッシュ通知の設定 |
sys_sg_offline_rule | オフラインルール |
モバイルアプリの設定(ES5)
モバイルアプリの作成
// Create mobile app configuration (ES5 ONLY!)
var app = new GlideRecord("sys_sg_mobile_app")
app.initialize()
app.setValue("name", "IT Support")
app.setValue("description", "Mobile app for IT support tasks")
// App settings
app.setValue("active", true)
app.setValue("version", "1.0.0")
// Branding
app.setValue("primary_color", "#1976D2")
app.setValue("secondary_color", "#FFFFFF")
app.setValue("icon", "attachment_sys_id")
// Default screen
app.setValue("home_screen", homeScreenSysId)
// Roles
app.setValue("roles", "itil")
app.insert()
モバイル画面の設定
// Create mobile screen (ES5 ONLY!)
var screen = new GlideRecord("sys_sg_screen")
screen.initialize()
screen.setValue("name", "My Incidents")
screen.setValue("mobile_app", mobileAppSysId)
screen.setValue("type", "list") // list, record, custom
// Data source
screen.setValue("table", "incident")
screen.setValue("filter", "assigned_to=javascript:gs.getUserID()^active=true")
// Display
screen.setValue("title", "My Incidents")
screen.setValue("icon", "list")
// Ordering
screen.setValue("order", 100)
screen.insert()
カードビルダー(ES5)
カード設定の作成
// Create card builder for list display (ES5 ONLY!)
var card = new GlideRecord("sys_sg_card_builder")
card.initialize()
card.setValue("name", "Incident Card")
card.setValue("table", "incident")
// Card layout
card.setValue("primary_field", "number")
card.setValue("secondary_field", "short_description")
card.setValue("tertiary_field", "priority")
// Additional fields
card.setValue(
"fields",
JSON.stringify([
{ field: "caller_id", label: "Caller" },
{ field: "state", label: "Status" },
{ field: "opened_at", label: "Opened" },
]),
)
// Visual indicators
card.setValue("color_field", "priority")
card.setValue(
"color_mapping",
JSON.stringify({
1: "#D32F2F", // Critical - Red
2: "#F57C00", // High - Orange
3: "#FBC02D", // Moderate - Yellow
4: "#388E3C", // Low - Green
5: "#1976D2", // Planning - Blue
}),
)
card.insert()
カスタムカードアクション
// Add actions to card (ES5 ONLY!)
function addCardAction(cardSysId, actionDef) {
var action = new GlideRecord("sys_sg_card_action")
action.initialize()
action.setValue("card_builder", cardSysId)
action.setValue("label", actionDef.label)
action.setValue("icon", actionDef.icon)
action.setValue("order", actionDef.order)
// Action type
action.setValue("action_type", actionDef.type) // script, navigate, share
// Script action (ES5 ONLY!)
if (actionDef.type === "script") {
action.setValue("script", actionDef.script)
}
action.insert()
}
// Example actions
addCardAction(cardSysId, {
label: "Acknowledge",
icon: "check",
order: 100,
type: "script",
script:
"(function(gr) {\n" +
" gr.state = 2; // In Progress\n" +
' gr.work_notes = "Acknowledged via mobile";\n' +
" gr.update();\n" +
' gs.addInfoMessage("Incident acknowledged");\n' +
"})(current);",
})
プッシュ通知(ES5)
プッシュ通知の設定
// Create push notification config (ES5 ONLY!)
var push = new GlideRecord("sys_push_notification")
push.initialize()
push.setValue("name", "High Priority Incident Assigned")
push.setValue("description", "Notify when high priority incident assigned")
// Target table and condition
push.setValue("table", "incident")
push.setValue("condition", "priority<=2^assigned_to.changes()")
// Notification content
push.setValue("title", "High Priority Incident Assigned")
push.setValue("body", "${number}: ${short_description}")
// Recipients
push.setValue("recipient_type", "field")
push.setValue("recipient_field", "assigned_to")
// Deep link
push.setValue("deep_link", true)
push.setValue("link_url", "/incident/${sys_id}")
push.setValue("active", true)
push.insert()
プログラムによるプッシュ通知の送信
// Send push notification (ES5 ONLY!)
function sendPushNotification(userSysId, message) {
try {
var push = new sn_notification.PushNotification()
push.setTitle(message.title)
push.setBody(message.body)
if (message.data) {
push.setData(message.data)
}
if (message.deepLink) {
push.setDeepLink(message.deepLink)
}
push.send(userSysId)
return { success: true }
} catch (e) {
gs.error("Push notification failed: " + e.message)
return { success: false, error: e.message }
}
}
// Example
sendPushNotification(userSysId, {
title: "Task Assigned",
body: "You have a new task assigned",
deepLink: "/task/" + taskSysId,
})
オフライン機能(ES5)
オフラインルールの設定
// Create offline sync rule (ES5 ONLY!)
var rule = new GlideRecord("sys_sg_offline_rule")
rule.initialize()
rule.setValue("name", "My Open Incidents")
rule.setValue("mobile_app", mobileAppSysId)
rule.setValue("table", "incident")
// Sync filter
rule.setValue("filter", "assigned_to=javascript:gs.getUserID()^active=true")
// Fields to sync
rule.setValue("fields", "number,short_description,description,priority,state,caller_id,opened_at")
// Related records
rule.setValue("include_references", true)
rule.setValue("reference_fields", "caller_id,assignment_group")
// Sync limits
rule.setValue("max_records", 100)
// Update frequency
rule.setValue("sync_frequency", "on_demand") // on_demand, periodic
rule.setValue("active", true)
rule.insert()
オフラインデータの処理
// Check for offline changes on sync (ES5 ONLY!)
function processOfflineChanges(userId) {
var offlineQueue = new GlideRecord("sys_sg_offline_queue")
offlineQueue.addQuery("user", userId)
offlineQueue.addQuery("processed", false)
offlineQueue.orderBy("created_on")
offlineQueue.query()
var results = { processed: 0, errors: [] }
while (offlineQueue.next()) {
try {
var tableName = offlineQueue.getValue("table")
var recordSysId = offlineQueue.getValue("record")
var changes = JSON.parse(offlineQueue.getValue("changes"))
// Apply changes
var gr = new GlideRecord(tableName)
if (gr.get(recordSysId)) {
for (var field in changes) {
if (changes.hasOwnProperty(field)) {
gr.setValue(field, changes[field])
}
}
gr.update()
results.processed++
}
// Mark as processed
offlineQueue.processed = true
offlineQueue.update()
} catch (e) {
results.errors.push({
record: offlineQueue.getValue("record"),
error: e.message,
})
}
}
return results
}
モバイルアクション(ES5)
モバイルアクションの作成
// Create mobile-specific action (ES5 ONLY!)
var action = new GlideRecord("sys_sg_action")
action.initialize()
action.setValue("name", "Scan Barcode")
action.setValue("label", "Scan Asset")
action.setValue("description", "Scan barcode to find asset")
// Action type
action.setValue("type", "native") // native, script, link
action.setValue("native_action", "barcode_scan")
// Available on
action.setValue("screens", screenSysIds)
// Callback script (ES5 ONLY!)
action.setValue(
"callback_script",
"(function(result) {\n" +
" if (!result.value) return;\n" +
" \n" +
' var asset = new GlideRecord("alm_asset");\n' +
' asset.addQuery("asset_tag", result.value);\n' +
" asset.query();\n" +
" \n" +
" if (asset.next()) {\n" +
" // Navigate to asset\n" +
' sn_mobile.navigate("record", {\n' +
' table: "alm_asset",\n' +
" sys_id: asset.getUniqueValue()\n" +
" });\n" +
" } else {\n" +
' gs.addErrorMessage("Asset not found: " + result.value);\n' +
" }\n" +
"})(scanResult);",
)
action.insert()
位置情報ベースのアクション
// Get user location for mobile (ES5 ONLY!)
// Available in mobile context
function getCurrentLocation() {
try {
var location = sn_mobile.getLocation()
return {
latitude: location.latitude,
longitude: location.longitude,
accuracy: location.accuracy,
}
} catch (e) {
return null
}
}
// Use location for nearby assets
function findNearbyAssets(latitude, longitude, radiusMeters) {
var assets = []
var gr = new GlideRecord("alm_asset")
gr.addNotNullQuery("location.latitude")
gr.query()
while (gr.next()) {
var assetLat = parseFloat(gr.location.latitude)
var assetLon = parseFloat(gr.location.longitude)
var distance = calculateDistance(latitude, longitude, assetLat, assetLon)
if (distance <= radiusMeters) {
assets.push({
sys_id: gr.getUniqueValue(),
name: gr.getDisplayValue(),
distance: Math.round(distance),
})
}
}
return assets.sort(function (a, b) {
return a.distance - b.distance
})
}
MCP ツール統合
利用可能なツール
| ツール | 用途 |
|---|---|
snow_query_table | モバイル設定のクエリ |
snow_execute_script | モバイルスクリプトのテスト |
snow_artifact_manage (action='find') | 設定の検索 |
ワークフローの例
// 1. Query mobile apps
await snow_query_table({
table: "sys_sg_mobile_app",
query: "active=true",
fields: "name,description,version,roles",
})
// 2. Get push notification configs
await snow_query_table({
table: "sys_push_notification",
query: "active=true",
fields: "name,table,condition,title",
})
// 3. Check offline rules
await snow_query_table({
table: "sys_sg_offline_rule",
query: "active=true",
fields: "name,table,filter,max_records",
})
ベストプラクティス
- オフラインファースト - 接続に問題がある場合を想定して設計します
- データを最小限に - 必要なフィールドのみを同期します
- プッシュ通知を適切に - 通知を送りすぎないようにします
- ネイティブ機能 - カメラ、GPS、バーコードを使用します
- カードの設計 - 重要な情報をひと目で確認できるようにします
- パフォーマンス - モバイル向けに最適化します
- テスト - 実機でテストします
- ES5 のみ - モダンな JavaScript 構文は使用しません
コメント
Markdown 対応