client-scripts について
ブラウザー側のフォーム動作を実装するServiceNow開発者向けのクライアントスクリプトスキルです。スクリプト種別、g_form、GlideAjax、入力検証、性能に関するパターンを扱います。
主な機能
- 014種類のクライアントスクリプトの説明
- 02g_form APIの使用例
- 03GlideAjaxによるクライアント・サーバー連携
- 04フォーム検証と条件付きフィールド
- 05サーバー呼び出しとイベント処理の最適化
活用シーン
- 01フォーム読み込み時の初期値設定
- 02値の変更に応じたフィールド更新
- 03フォーム送信内容の検証
- 04GlideAjaxによるサーバーデータの取得
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ serac-labs/ serac --skill client-scriptsインストール後、Claude Code で/client-scriptsを入力して読み込まれたか確認します。
詳細
ServiceNow のクライアントスクリプトのパターン
クライアントスクリプトはユーザーのブラウザーで実行され、フォームの動作を制御します。サーバーサイドスクリプトとは異なり、クライアントスクリプトでは最新のブラウザーでモダンな JavaScript(ES6 以降)を使用できます。
クライアントスクリプトの種類
| 種類 | 実行タイミング | 用途 |
|---|---|---|
| onLoad | フォームの読み込み時 | デフォルト値の設定、フィールドの表示/非表示、初期設定 |
| onChange | フィールド値の変更時 | ユーザー入力への対応、連動する更新 |
| onSubmit | フォームの送信時 | 保存前の検証 |
| onCellEdit | リストセルの編集時 | インライン編集の検証 |
g_form API
値の取得と設定
// Get field value
var priority = g_form.getValue("priority")
var callerName = g_form.getDisplayValue("caller_id") // Reference display value
// Set field value
g_form.setValue("priority", "1")
g_form.setValue("assigned_to", userSysId, "John Smith") // Reference with display
// Clear a field
g_form.clearValue("assignment_group")
フィールドの表示状態と設定
// Show/Hide fields
g_form.setVisible("u_internal_notes", false)
g_form.setDisplay("u_internal_notes", false) // Removes from DOM
// Make field mandatory
g_form.setMandatory("short_description", true)
// Make field read-only
g_form.setReadOnly("caller_id", true)
// Disable field (grayed out but visible)
g_form.setDisabled("state", true)
メッセージと検証
// Field-level messages
g_form.showFieldMsg("email", "Invalid email format", "error")
g_form.hideFieldMsg("email")
// Form-level messages
g_form.addInfoMessage("Record saved successfully")
g_form.addErrorMessage("Please fix the errors below")
g_form.clearMessages()
// Flash a field to draw attention
g_form.flash("priority", "#ff0000", 0) // Red flash
セクションとラベル
// Collapse/Expand sections
g_form.setSectionDisplay("notes", false) // Collapse
g_form.setSectionDisplay("notes", true) // Expand
// Change field label
g_form.setLabelOf("short_description", "Issue Summary")
よく使われるパターン
パターン 1:onLoad - デフォルト値の設定
function onLoad() {
// Only on new records
if (g_form.isNewRecord()) {
// Set default priority
g_form.setValue("priority", "3")
// Set caller to current user
g_form.setValue("caller_id", g_user.userID)
// Hide internal fields from end users
if (!g_user.hasRole("itil")) {
g_form.setVisible("assignment_group", false)
g_form.setVisible("assigned_to", false)
}
}
}
パターン 2:onChange - 連動する更新
function onChange(control, oldValue, newValue, isLoading) {
// Don't run during form load
if (isLoading) return
// When category changes, clear subcategory
if (newValue != oldValue) {
g_form.setValue("subcategory", "")
g_form.clearValue("u_item")
}
// Auto-set priority based on category
if (newValue == "security") {
g_form.setValue("priority", "1")
g_form.setReadOnly("priority", true)
} else {
g_form.setReadOnly("priority", false)
}
}
パターン 3:GlideAjax を使用した onChange
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading || newValue == "") return
// Get data from server
var ga = new GlideAjax("MyScriptInclude")
ga.addParam("sysparm_name", "getUserDetails")
ga.addParam("sysparm_user_id", newValue)
ga.getXMLAnswer(function (response) {
var data = JSON.parse(response)
// Update form with server data
g_form.setValue("location", data.location)
g_form.setValue("department", data.department)
g_form.setValue("u_vip", data.vip)
if (data.vip == "true") {
g_form.setValue("priority", "1")
g_form.flash("priority", "#ffff00", 2)
}
})
}
パターン 4:onSubmit - 検証
function onSubmit() {
// Validate email format
var email = g_form.getValue("u_email")
if (email && !isValidEmail(email)) {
g_form.showFieldMsg("u_email", "Please enter a valid email", "error")
return false // Prevent submit
}
// Require close notes when resolving
var state = g_form.getValue("state")
var closeNotes = g_form.getValue("close_notes")
if (state == "6" && !closeNotes) {
g_form.showFieldMsg("close_notes", "Close notes required", "error")
g_form.setMandatory("close_notes", true)
return false
}
// Confirm before high-priority submission
var priority = g_form.getValue("priority")
if (priority == "1") {
return confirm("This will create a Priority 1 incident. Continue?")
}
return true // Allow submit
}
function isValidEmail(email) {
var regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return regex.test(email)
}
パターン 5:条件付き必須フィールド
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading) return
// Category "Hardware" requires asset tag
var isHardware = newValue == "hardware"
g_form.setMandatory("u_asset_tag", isHardware)
g_form.setDisplay("u_asset_tag", isHardware)
// Category "Software" requires application name
var isSoftware = newValue == "software"
g_form.setMandatory("u_application", isSoftware)
g_form.setDisplay("u_application", isSoftware)
}
GlideAjax パターン(サーバーとの通信)
クライアントスクリプト
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading || !newValue) return
var ga = new GlideAjax("IncidentUtils")
ga.addParam("sysparm_name", "getRelatedIncidents")
ga.addParam("sysparm_ci", newValue)
ga.getXMLAnswer(handleResponse)
}
function handleResponse(response) {
var result = JSON.parse(response)
if (result.count > 0) {
g_form.addWarningMessage("There are " + result.count + " related open incidents for this CI")
}
}
サーバー側スクリプトインクルード
var IncidentUtils = Class.create()
IncidentUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
getRelatedIncidents: function () {
var ci = this.getParameter("sysparm_ci")
var result = { count: 0, incidents: [] }
var gr = new GlideRecord("incident")
gr.addQuery("cmdb_ci", ci)
gr.addQuery("active", true)
gr.query()
result.count = gr.getRowCount()
while (gr.next()) {
result.incidents.push({
number: gr.getValue("number"),
short_description: gr.getValue("short_description"),
})
}
return JSON.stringify(result)
},
type: "IncidentUtils",
})
g_user オブジェクト
// Current user information
var userName = g_user.userName // User name
var userID = g_user.userID // sys_id
var firstName = g_user.firstName // First name
var lastName = g_user.lastName // Last name
var fullName = g_user.getFullName() // Full name
// Role checks
if (g_user.hasRole("admin")) {
}
if (g_user.hasRole("itil")) {
}
if (g_user.hasRoleExactly("incident_manager")) {
} // Exact match, no admin override
// Multiple roles
if (g_user.hasRoleFromList("itil,incident_manager")) {
}
パフォーマンスのベストプラクティス
1. サーバー呼び出しを最小限にする
// ❌ BAD - Multiple GlideAjax calls
onChange: getUserLocation()
onChange: getUserDepartment()
onChange: getUserManager()
// ✅ GOOD - Single call returning all data
onChange: getUserDetails() // Returns location, department, manager
2. isLoading パラメーターを使用する
function onChange(control, oldValue, newValue, isLoading) {
// ❌ BAD - Runs during form load
callServer(newValue)
// ✅ GOOD - Skip during load
if (isLoading) return
callServer(newValue)
}
3. 急速な変更をデバウンスする
var timeout
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading) return
clearTimeout(timeout)
timeout = setTimeout(function () {
performExpensiveOperation(newValue)
}, 300) // Wait 300ms for typing to stop
}
よくある間違い
| 間違い | 問題 | 解決策 |
|---|---|---|
isLoading の確認を忘れる | 読み込み時にスクリプトが不要に実行される | 常に if (isLoading) return; を確認する |
| onSubmit をブロックする | 検証に時間がかかると UI がフリーズする | コールバックを使った非同期検証を使用する |
| GlideAjax でエラーハンドリングを行わない | エラーが通知されない | エラーコールバックを追加する |
| 1 つのブラウザーでしかテストしない | ブラウザー間の問題が発生する | Chrome、Firefox、Edge でテストする |
| DOM を直接操作する | UI の更新時に動作しなくなる | g_form API を使用する |
コメント
Markdown 対応