UNPKG

@servicenow/sdk

Version:
993 lines (797 loc) 32.8 kB
--- tags: [catalog variable, variable set, catalog ui policy, catalog client script, item_option_new, item_option_new_set, catalog_ui_policy, catalog_script_client, onChange, onLoad, onSubmit, g_form, dynamic fields] --- # Service Catalog Variables API reference and patterns for Service Catalog variables, variable sets, UI policies, and client scripts. For catalog items and record producers, see [service-catalog-guide.md](service-catalog-guide.md). Requires SDK 4.3.0 or higher. --- ## Catalog Variables API Reference ### Common Variable Properties | Property | Type | Description | |---|---|---| | `question` | string | **Required.** Label text displayed to user. | | `order` | number | Display order (use increments of 100). | | `mandatory` | boolean | Whether field is required. Default: `false`. | | `readOnly` | boolean | Whether field is editable. Default: `false`. | | `hidden` | boolean | Whether field is visible. Default: `false`. | | `tooltip` | string | Hover help text. | | `exampleText` | string | Placeholder text. | | `instructions` | string | Inline help text. | | `defaultValue` | string | Pre-filled value. | | `width` | 25 \| 50 \| 75 \| 100 | Field width percentage. | | `readRoles` | string[] | Roles that can read the variable. | | `writeRoles` | string[] | Roles that can write to the variable. | | `mapToField` | boolean | Map to target table field (Record Producers). | | `field` | string | Target field name when mapToField is true. | ### Variable Types **Text Variables** - **SingleLineTextVariable** -- Single line text input - **MultiLineTextVariable** -- Multi-line text area - **WideSingleLineTextVariable** -- Full-width single line - **EmailVariable** -- Email address input - **UrlVariable** -- URL input - **IpAddressVariable** -- IPv4/IPv6 input - **MaskedVariable** -- Masked/password input (supports `useEncryption`, `useConfirmation`) **Choice Variables** - **SelectBoxVariable** -- Dropdown choice list. Requires `choices` object with `{ label, sequence }`. - **MultipleChoiceVariable** -- Radio buttons. Supports `choiceDirection: 'down'` or `'across'`. - **YesNoVariable** -- Yes/No choice list. - **CheckboxVariable** -- Checkbox. Use `selectionRequired: true` for mandatory. - **NumericScaleVariable** -- Likert scale radio buttons. **Lookup Variables** - **LookupSelectBoxVariable** -- Dropdown from table data. - **LookupMultipleChoiceVariable** -- Radio buttons from table data. **Reference Variables** - **ReferenceVariable** -- References a record in another table. Key properties: `referenceTable`, `referenceQualCondition`, `useReferenceQualifier`. - **RequestedForVariable** -- Specifies who the request is for. - **ListCollectorVariable** -- Select multiple records from a table. **Date/Time Variables** - **DateVariable** -- Date picker. - **DateTimeVariable** -- Date and time picker. - **DurationVariable** -- Duration input. **Layout Variables** - **ContainerStartVariable** / **ContainerSplitVariable** / **ContainerEndVariable** -- Multi-column layout containers. Must be properly paired. - **LabelVariable** -- Display-only label. - **BreakVariable** -- Horizontal line separator. **Special Variables** - **AttachmentVariable** -- File upload. - **HtmlVariable** -- Rich content display. - **RichTextLabelVariable** -- Formatted label. - **CustomVariable** / **CustomWithLabelVariable** -- UI macro insertion. - **UIPageVariable** -- UI page insertion. --- ## Variable Set API Reference ### Properties | Property | Type | Description | |---|---|---| | `$id` | Now.ID[string] | **Required.** Unique identifier. | | `title` | string | **Required.** Display title. | | `internalName` | string | Internal name. Auto-generated from title if not provided. | | `description` | string | Description of the variable set. | | `type` | `'singleRow'` \| `'multiRow'` | Default: `'singleRow'`. | | `layout` | `'normal'` \| `'2down'` \| `'2across'` | Default: `'normal'`. | | `order` | number | Display order. Default: `100`. | | `displayTitle` | boolean | Show collapsible section header. Default: `false`. | | `setAttributes` | string | Additional config (e.g., `"max_rows=10,collapsible=true"`). | | `readRoles` | string[] | Roles that can view the variable set. | | `writeRoles` | string[] | Roles that can modify values. | | `createRoles` | string[] | Roles that can create instances (for multiRow). | | `variables` | object | Variable definitions for the set. | ### Attaching to Catalog Items Attach variable sets via `variableSets: [{ variableSet, order }]` on a Catalog Item or Record Producer. Item-specific variables can be added alongside variable sets. ### Multi-Row Variable Set (MRVS) Use `type: "multiRow"` for grid/table data entry (e.g., multiple team members). Configure with `setAttributes` for row limits and collapsibility. ### MRVS Unsupported Variable Types - AttachmentVariable - ContainerStartVariable / ContainerEndVariable / ContainerSplitVariable - HtmlVariable - CustomVariable / CustomWithLabelVariable - RichTextLabelVariable - UIPageVariable ### MRVS Limitations - "Assign to Field" not supported - Cannot add variables with read roles - Set row limits using `max_rows` attribute - Will not display if added to a container ### Role-Based Access - `readRoles`: Roles that can view the variable set - `writeRoles`: Roles that can modify values - `createRoles`: Roles that can create instances (multiRow) Set-level permissions override variable-level permissions when access is denied at the set level. --- ## Catalog UI Policy API Reference ### Properties | Property | Type | Description | |---|---|---| | `$id` | Now.ID[string] | **Required.** Unique identifier. | | `shortDescription` | string | **Required.** Description of what the policy does. | | `catalogItem` | ref | **Required** if not using variableSet. | | `variableSet` | ref | **Required** if not using catalogItem. | | `appliesTo` | `'item'` \| `'set'` | Required if using variableSet. Default: `'item'`. | | `active` | boolean | Whether the policy is active. Default: `true`. | | `onLoad` | boolean | Run on form load. Default: `true`. | | `reverseIfFalse` | boolean | Reverse actions when condition is false. Default: `true`. | | `catalogCondition` | string | Condition using encoded query syntax. | | `appliesOnCatalogItemView` | boolean | Applies to catalog item view. Default: `true`. | | `appliesOnTargetRecord` | boolean | Applies to target record. Default: `false`. | | `appliesOnCatalogTasks` | boolean | Applies to catalog tasks. Default: `false`. | | `appliesOnRequestedItems` | boolean | Applies to requested items. Default: `false`. | | `runScripts` | boolean | Execute client scripts. Default: `false`. | | `executeIfTrue` | string | Script when condition is true. | | `executeIfFalse` | string | Script when condition is false. | | `runScriptsInUiType` | `'desktop'` \| `'mobileOrServicePortal'` \| `'all'` | Default: `'desktop'`. | | `actions` | array | List of variable actions. | ### Action Properties | Property | Type | Description | |---|---|---| | `variableName` | ref | **Required.** Variable reference. | | `visible` | boolean | Show/hide the variable. | | `mandatory` | boolean | Make variable required. | | `readOnly` | boolean | Make variable read-only. | | `value` | string | Value to set. | | `valueAction` | `'clearValue'` \| `'setValue'` | How to apply the value. | | `order` | number | Execution order. Default: `100`. | | `variableMessage` | string | Message to display on the field. | | `variableMessageType` | `'info'` \| `'warning'` \| `'error'` | Message severity. | ### Condition Syntax ```typescript fluent // Simple condition catalogCondition: `${catalogItem.variables.priority}=high^EQ`; // Multiple conditions with AND catalogCondition: `${catalogItem.variables.env}=prod^${catalogItem.variables.critical}=true^EQ`; // Multiple conditions with OR catalogCondition: `${catalogItem.variables.env}=prod^OR${catalogItem.variables.critical}=true^EQ`; // Not empty check catalogCondition: `${catalogItem.variables.reference}ISNOTEMPTY^EQ`; ``` ### Priority Rules 1. **Mandatory** has highest priority 2. If a variable is mandatory and has no value, readonly/hide actions **do not work** 3. If a variable set/container has a mandatory variable without value, the entire set **cannot be hidden** 4. "Clear value" action does not work on variable sets and containers ### Variable Type Limitations | Policy Type | Not Applicable To | |---|---| | Mandatory | Fraction, Container Split, Container End, UI Macro, Label, UI Page | | Read-only | Fraction, Container Split, Container End, UI Macro, Label, UI Page | | Visibility | Fraction, Container Split, Container End | ### Policy with Client Scripts Set `runScripts: true` and provide `executeIfTrue` / `executeIfFalse` scripts via `Now.include(...)`. These scripts run client-side in the browser where modules are not available, so `Now.include()` is the correct approach. Scripts must be wrapped in `function onCondition() {}`. --- ## Catalog Client Script API Reference ### Properties | Property | Type | Description | |---|---|---| | `$id` | Now.ID[string] | **Required.** Unique identifier. | | `name` | string | **Required.** Name of the script. | | `script` | string | Inline script or `Now.include()` reference. These are client-side scripts — modules are not available. | | `type` | `'onLoad'` \| `'onChange'` \| `'onSubmit'` | Script trigger type. | | `uiType` | `'desktop'` \| `'mobileOrServicePortal'` \| `'all'` | Default: `'desktop'`. | | `active` | boolean | Whether the script is enabled. Default: `true`. | | `catalogItem` | ref | **Required** if not using variableSet. | | `variableSet` | ref | **Required** if not using catalogItem. | | `appliesTo` | `'item'` \| `'set'` | Required if using variableSet. Default: `'item'`. | | `variableName` | ref | **Required** for onChange. The variable that triggers the script. | | `appliesOnCatalogItemView` | boolean | Applies on catalog item view. Default: `true`. | | `appliesOnRequestedItems` | boolean | Applies on requested items. Default: `false`. | | `appliesOnCatalogTasks` | boolean | Applies on catalog tasks. Default: `false`. | | `appliesOnTargetRecord` | boolean | Applies on target record. Default: `false`. | ### Script Types **onLoad** -- Runs when the form loads. Use for initial setup (field states, defaults, visibility). **onChange** -- Runs when a specific variable changes. Always guard with `if (isLoading) return;` to prevent execution during form load. **onSubmit** -- Runs on form submission. Return `false` to block submission. Avoid GlideAjax here -- async calls will not complete before the form submits. ### g_form API Reference | Method | Description | |---|---| | `getValue(fieldName)` | Get variable value | | `setValue(fieldName, value)` | Set variable value | | `setDisplay(fieldName, display)` | Show/hide variable | | `setMandatory(fieldName, mandatory)` | Set mandatory state | | `setReadOnly(fieldName, readOnly)` | Set read-only state | | `clearValue(fieldName)` | Clear variable value | | `hasField(fieldName)` | Check if field exists | | `showFieldMsg(fieldName, message, type, scrollForm)` | Show field message | | `hideFieldMsg(fieldName, clearAll)` | Hide field message | | `addErrorMessage(message)` | Add banner error message | | `clearOptions(fieldName)` | Clear all select options | | `addOption(fieldName, value, label)` | Add a select option | | `getReference(fieldName, callback)` | Get referenced record (legacy) | Note on `getReference`: Legacy convenience method. Works for simple lookups but `GlideAjax` is preferred for complex server-side logic. May make synchronous calls in some versions, which can freeze the UI. ### Catalog Client Script vs Standard Client Script | Aspect | Catalog Client Script | Standard Client Script | |---|---|---| | Scope | Catalog item or variable set | Table (e.g., Incident) | | onChange target | Links to a **variable** | Links to a **field** | | Context | Catalog ordering, RITM, Catalog Task forms | Table forms | | Variable access | Direct by variable name | Use `variables.variable_name` prefix | | Applies to | `item` or `set` | Specific table | ### Scripts on Variable Sets Scope scripts to a variable set using `variableSet` and `appliesTo: 'set'` so they apply to **all** catalog items using that set. Always use `hasField()` checks since the variable may not exist on every item that includes the set. When multiple variable sets are attached to a catalog item, scripts execute in the order the variable sets are listed on the item. If both a variable set script and an item-level script target the same variable, the item-level script runs last and takes precedence. ### GlideAjax Use `GlideAjax` to call server-side Script Includes from client scripts. The client sends a request, the Script Include processes it, and returns a result via a callback. **Method comparison:** | Method | Execution | Use When | Avoid When | |---|---|---|---| | `getXMLAnswer()` | **Async** | Simple lookups, returning a single value/string | You need the full XML response object | | `getXML()` | **Async** | Need full XML response, complex response parsing | Simple value returns (use getXMLAnswer) | | `getXMLWait()` | **Sync** | Almost never -- legacy/global scope only | Scoped apps, any production code | **Parameter rules:** All custom parameters must start with `sysparm_`. The first `addParam` call must always be `sysparm_name` with the method name. ```javascript ga.addParam("sysparm_name", "methodName"); // REQUIRED: always first ga.addParam("sysparm_user_id", userSysId); // Custom param: prefix with sysparm_ ``` ### Script Include (Server-Side Companion) Every `GlideAjax` call requires a corresponding **Script Include** on the server. The Script Include must extend `AbstractAjaxProcessor` and be marked **Client Callable**. | Property | Value | |---|---| | Name | Must match the string in `new GlideAjax('ClassName')` | | Client callable | **Checked** (required for GlideAjax access) | | Extends | `global.AbstractAjaxProcessor` | | Retrieve params | Use `this.getParameter('sysparm_param_name')` | | Return data | Use `return` (simple string) or `return JSON.stringify(obj)` for objects | **Security:** Client callable Script Includes run in the logged-in user's session context. ACLs still apply to GlideRecord queries. Always validate parameters from `this.getParameter()`. Never trust client-side input. --- ## Examples ### Variable Examples **Text variables:** ```typescript fluent import { SingleLineTextVariable, MultiLineTextVariable, EmailVariable, MaskedVariable } from '@servicenow/sdk/core' SingleLineTextVariable({ question: "Employee Name", order: 100, mandatory: true, exampleText: "John Smith" }); MultiLineTextVariable({ question: "Description", order: 200, mandatory: true, width: 100 }); EmailVariable({ question: "Email Address", order: 300, mandatory: true }); MaskedVariable({ question: "Enter Password", order: 400, useEncryption: true }); ``` **Choice variables:** ```typescript fluent import { SelectBoxVariable, MultipleChoiceVariable, CheckboxVariable } from '@servicenow/sdk/core' SelectBoxVariable({ question: "Priority Level", order: 100, choices: { high: { label: "High", sequence: 1 }, medium: { label: "Medium", sequence: 2 }, low: { label: "Low", sequence: 3 } }, includeNone: true }); MultipleChoiceVariable({ question: "Services Required", choiceDirection: "down", choices: { install: { label: "Installation", sequence: 1 }, config: { label: "Configuration", sequence: 2 }, training: { label: "Training", sequence: 3 } }, order: 200 }); CheckboxVariable({ question: "I agree to the terms", order: 400, selectionRequired: true }); ``` **Reference variables:** ```typescript fluent import { ReferenceVariable, ListCollectorVariable } from '@servicenow/sdk/core' ReferenceVariable({ question: "Point of Contact", referenceTable: "sys_user", referenceQualCondition: "active=true", order: 100 }); ListCollectorVariable({ question: "Team Members", listTable: "sys_user", referenceQual: "active=true", order: 300, mandatory: true }); ``` **Container layout (multi-column):** ```typescript fluent import { ContainerStartVariable, ContainerSplitVariable, ContainerEndVariable, SingleLineTextVariable, EmailVariable } from '@servicenow/sdk/core' const variables = { contact_container_start: ContainerStartVariable({ question: "Contact Information", layout: "2across", displayTitle: true, order: 100 }), first_name: SingleLineTextVariable({ question: "First Name", mandatory: true, order: 110 }), contact_split: ContainerSplitVariable({ order: 200 }), email: EmailVariable({ question: "Email Address", mandatory: true, order: 210 }), contact_container_end: ContainerEndVariable({ order: 300 }) } ``` **Variables with pricing:** ```typescript fluent import { CheckboxVariable, SelectBoxVariable } from '@servicenow/sdk/core' premiumSupport: CheckboxVariable({ question: "Premium Support (+$150)", pricingDetails: [ { amount: 150, currencyType: "USD", field: "price_if_checked" }, { amount: 30, currencyType: "USD", field: "rec_price_if_checked" } ], order: 500 }); hardwareType: SelectBoxVariable({ question: "Hardware Type", choices: { laptop: { label: "Business Laptop (Base)", sequence: 1, pricingDetails: [{ amount: 0, currencyType: "USD", field: "misc" }] }, workstation: { label: "Developer Workstation (+$800)", sequence: 2, pricingDetails: [{ amount: 800, currencyType: "USD", field: "misc" }] } }, mandatory: true, order: 600 }); ``` ### Single-Row Variable Set ```typescript fluent import { VariableSet, EmailVariable, SingleLineTextVariable, ReferenceVariable } from "@servicenow/sdk/core"; export const contactInfoSet = VariableSet({ $id: Now.ID["contact_info_set"], title: "Contact Information", description: "Standard contact information fields", type: "singleRow", layout: "2across", order: 100, displayTitle: true, variables: { email: EmailVariable({ question: "Email Address", mandatory: true, order: 100 }), phone: SingleLineTextVariable({ question: "Phone Number", mandatory: true, order: 200 }), department: ReferenceVariable({ question: "Department", referenceTable: "cmn_department", referenceQualCondition: "active=true", order: 300 }) } }); ``` ### Multi-Row Variable Set (MRVS) ```typescript fluent import { VariableSet, ReferenceVariable, SelectBoxVariable, DateVariable } from '@servicenow/sdk/core' export const teamMembersSet = VariableSet({ $id: Now.ID["team_members_set"], title: "Team Members", description: "Add multiple team members who need access", type: "multiRow", layout: "2across", displayTitle: true, setAttributes: "max_rows=10,collapsible=true", readRoles: ["admin", "manager"], writeRoles: ["admin"], variables: { user: ReferenceVariable({ question: "User", referenceTable: "sys_user", referenceQualCondition: "active=true", mandatory: true, order: 100 }), accessLevel: SelectBoxVariable({ question: "Access Level", choices: { read: { label: "Read Only", sequence: 1 }, write: { label: "Write", sequence: 2 }, admin: { label: "Admin", sequence: 3 } }, mandatory: true, order: 200 }), startDate: DateVariable({ question: "Access Start Date", mandatory: true, order: 300 }) } }); ``` ### Attaching Variable Sets to a Catalog Item ```typescript fluent import { CatalogItem, MultiLineTextVariable } from '@servicenow/sdk/core' import { contactInfoSet } from './variable-sets/contact-info-set' import { teamMembersSet } from './variable-sets/team-members-set' import { serviceCatalog } from './catalogs/service-catalog' import { itServicesCategory } from './categories/it-services' export const accessRequest = CatalogItem({ $id: Now.ID["access_request"], name: "Team Access Request", shortDescription: "Request access for team members", catalogs: [serviceCatalog], categories: [itServicesCategory], variableSets: [ { variableSet: contactInfoSet, order: 100 }, { variableSet: teamMembersSet, order: 200 } ], variables: { notes: MultiLineTextVariable({ question: "Additional Notes", order: 100 }) }, flow: "523da512c611228900811a37c97c2014" }); ``` ### Catalog UI Policy -- Show/Hide Based on Condition ```typescript fluent import { CatalogUiPolicy } from "@servicenow/sdk/core"; import { hardwareRequestItem } from "./catalog-items/HardwareRequest.now"; export const managerApprovalPolicy = CatalogUiPolicy({ $id: Now.ID["manager_approval_policy"], shortDescription: "Show manager approval when high priority selected", catalogItem: hardwareRequestItem, catalogCondition: `${hardwareRequestItem.variables.priority}=high^EQ`, actions: [ { variableName: hardwareRequestItem.variables.manager_approval, visible: true, mandatory: true } ] }); ``` ### Catalog UI Policy -- Read-Only with Value ```typescript fluent import { CatalogUiPolicy } from "@servicenow/sdk/core" import { softwareRequestItem } from './catalog-items/software-request' export const readOnlyPolicy = CatalogUiPolicy({ $id: Now.ID["readonly_license_policy"], shortDescription: "Make license type read-only for standard software", catalogItem: softwareRequestItem, catalogCondition: `${softwareRequestItem.variables.software_type}=standard^EQ`, actions: [ { variableName: softwareRequestItem.variables.license_type, readOnly: true, value: "standard_license", valueAction: "setValue" } ] }); ``` ### Catalog UI Policy -- With Client Scripts ```typescript fluent import { CatalogUiPolicy } from "@servicenow/sdk/core" import { cloudVmRequest } from './catalog-items/cloud-vm-request' CatalogUiPolicy({ $id: Now.ID["vm_prod_controls_policy"], shortDescription: "VM: Prod/BizCritical Controls", catalogItem: cloudVmRequest, catalogCondition: `${cloudVmRequest.variables.environment}=prod^OR${cloudVmRequest.variables.business_critical}=true^EQ`, active: true, onLoad: true, reverseIfFalse: true, runScripts: true, runScriptsInUiType: "all", actions: [ { variableName: cloudVmRequest.variables.backup_required, value: "true", valueAction: "setValue", readOnly: true, order: 100 }, { variableName: cloudVmRequest.variables.cost_center, mandatory: true, order: 200 } ], executeIfTrue: Now.include("../../scripts/vm-production-controls.js"), executeIfFalse: Now.include("../../scripts/vm-development-controls.js") }); ``` **vm-production-controls.js:** ```javascript function onCondition() { var PROD_REGIONS = [ ["AP-South-1", "AP-South-1 (Mumbai)"], ["EU-West-1", "EU-West-1 (Ireland)"] ]; g_form.clearOptions("region"); PROD_REGIONS.forEach(function (pair) { g_form.addOption("region", pair[0], pair[1]); }); g_form.showFieldMsg( "environment", "Production VMs enforce backup and require cost center.", "info" ); } ``` ### Catalog UI Policy -- Applied to Variable Set ```typescript fluent import { CatalogUiPolicy } from "@servicenow/sdk/core" import { shippingVariableSet } from './variable-sets/shipping' export const internationalShippingPolicy = CatalogUiPolicy({ $id: Now.ID["international_shipping_policy"], shortDescription: "Show customs fields for international shipping", variableSet: shippingVariableSet, appliesTo: "set", catalogCondition: `${shippingVariableSet.variables.shipping_country}!=US^EQ`, appliesOnCatalogItemView: true, appliesOnRequestedItems: true, actions: [ { variableName: shippingVariableSet.variables.customs_declaration, visible: true, mandatory: true, variableMessage: "Required for international shipping", variableMessageType: "warning" } ] }); ``` ### Catalog Client Script -- onLoad ```typescript fluent import { CatalogClientScript } from "@servicenow/sdk/core"; import { laptopRequest } from "../catalog-items/laptop-request.now"; CatalogClientScript({ $id: Now.ID["laptop_onload"], name: "Laptop Request - OnLoad", script: Now.include("../../client/laptop-onload.js"), type: "onLoad", catalogItem: laptopRequest, active: true, appliesOnCatalogItemView: true }); ``` **laptop-onload.js:** ```javascript function onLoad() { g_form.setReadOnly("estimated_cost", true); g_form.setValue("estimated_cost", "$0"); g_form.setMandatory("justification", true); } ``` ### Catalog Client Script -- onChange ```typescript fluent import { CatalogClientScript } from "@servicenow/sdk/core" import { laptopRequest } from '../catalog-items/laptop-request.now' CatalogClientScript({ $id: Now.ID["laptop_type_change"], name: "Laptop Type - onChange", script: Now.include("../../client/laptop-type-change.js"), type: "onChange", catalogItem: laptopRequest, variableName: laptopRequest.variables.laptopType, active: true }); ``` **laptop-type-change.js:** ```javascript function onChange(control, oldValue, newValue, isLoading) { if (isLoading) return; // Always guard against initial load if (newValue === "developer") { g_form.setDisplay("accessories", true); } else { g_form.setDisplay("accessories", false); g_form.clearValue("accessories"); } } ``` ### Catalog Client Script -- onSubmit Validation ```typescript fluent import { CatalogClientScript } from "@servicenow/sdk/core" import { laptopRequest } from '../catalog-items/laptop-request.now' CatalogClientScript({ $id: Now.ID["laptop_validation"], name: "Laptop Request - Validation", script: Now.include("../../client/laptop-validation.js"), type: "onSubmit", catalogItem: laptopRequest, active: true }); ``` **laptop-validation.js:** ```javascript function onSubmit() { var justification = (g_form.getValue("justification") || "").trim(); if (justification.length < 20) { g_form.showFieldMsg("justification", "Please provide at least 20 characters.", "error", true); g_form.addErrorMessage("Justification is too short."); return false; } return true; } ``` ### Catalog Client Script -- onChange with GlideAjax ```typescript fluent import { CatalogClientScript } from "@servicenow/sdk/core" import { equipmentRepairItem } from '../catalog-items/equipment-repair' CatalogClientScript({ $id: Now.ID["asset_tag_lookup"], name: "Asset Tag - Warranty Lookup", script: Now.include("../../client/asset-tag-lookup.js"), type: "onChange", catalogItem: equipmentRepairItem, variableName: equipmentRepairItem.variables.asset_tag, active: true }); ``` **asset-tag-lookup.js:** ```javascript function onChange(control, oldValue, newValue, isLoading) { if (isLoading) return; if (!newValue) { g_form.clearValue("warranty_status"); return; } var ga = new GlideAjax("global.AssetLookupUtil"); ga.addParam("sysparm_name", "getWarrantyStatus"); ga.addParam("sysparm_asset_tag", newValue); ga.getXMLAnswer(function (response) { if (!response) return; var info = JSON.parse(response); g_form.setValue("warranty_status", info.status); }); } ``` ### Catalog Client Script -- Scoped to Variable Set ```typescript fluent import { CatalogClientScript } from "@servicenow/sdk/core"; import { requesterInfoSet } from "./variable-sets/requester-info-set.now"; CatalogClientScript({ $id: Now.ID["department_change_script"], name: "Department Change - Clear Manager", type: "onChange", variableSet: requesterInfoSet, appliesTo: "set", variableName: requesterInfoSet.variables.department, script: Now.include("../../client/department-change.js"), active: true, uiType: "all" }); ``` **department-change.js:** ```javascript function onChange(control, oldValue, newValue, isLoading) { if (isLoading) return; g_form.clearValue("manager"); if (!newValue) return; g_form.showFieldMsg("manager", "Please select a manager from the new department", "info", false); } ``` ### GlideAjax -- Dynamic Options Based on Selection **Client script (onChange on 'department' variable):** ```javascript function onChange(control, oldValue, newValue, isLoading) { if (isLoading) return; g_form.clearOptions("category"); g_form.addOption("category", "", "-- Select --"); if (!newValue) return; var ga = new GlideAjax("CatalogOptionLoader"); ga.addParam("sysparm_name", "getCategoriesByDept"); ga.addParam("sysparm_department", newValue); ga.getXMLAnswer(function (answer) { if (!answer) return; var categories = JSON.parse(answer); categories.forEach(function (cat) { g_form.addOption("category", cat.value, cat.label); }); }); } ``` **Script Include (CatalogOptionLoader, Client callable = true):** ```javascript var CatalogOptionLoader = Class.create(); CatalogOptionLoader.prototype = Object.extendsObject(global.AbstractAjaxProcessor, { getCategoriesByDept: function () { var deptId = this.getParameter("sysparm_department"); var categories = []; var gr = new GlideRecord("sc_category"); gr.addQuery("department", deptId); gr.addQuery("active", true); gr.orderBy("title"); gr.query(); while (gr.next()) { categories.push({ value: gr.getUniqueValue(), label: gr.getValue("title") }); } return JSON.stringify(categories); }, type: "CatalogOptionLoader" }); ``` ### GlideAjax -- Server-Side Validation (getXML) **Client script (onChange on 'asset_tag' variable):** ```javascript function onChange(control, oldValue, newValue, isLoading) { if (isLoading) return; g_form.hideFieldMsg("asset_tag", true); if (!newValue) { g_form.clearValue("configuration_item"); return; } var ga = new GlideAjax("AssetValidator"); ga.addParam("sysparm_name", "validateAssetTag"); ga.addParam("sysparm_asset_tag", newValue); ga.getXML(function (response) { var answer = response.responseXML.documentElement.getAttribute("answer"); if (!answer) { g_form.showFieldMsg("asset_tag", "Unable to validate. Try again.", "error"); return; } var result = JSON.parse(answer); if (result.found) { g_form.setValue("configuration_item", result.ci_sys_id); g_form.showFieldMsg("asset_tag", "Found: " + result.ci_name, "info"); } else { g_form.clearValue("configuration_item"); g_form.showFieldMsg("asset_tag", "Asset tag not found in CMDB.", "error"); } }); } ``` **Script Include (AssetValidator, Client callable = true):** ```javascript var AssetValidator = Class.create(); AssetValidator.prototype = Object.extendsObject(global.AbstractAjaxProcessor, { validateAssetTag: function () { var assetTag = this.getParameter("sysparm_asset_tag"); if (!assetTag) { return JSON.stringify({ found: false, error: "No asset tag provided" }); } var gr = new GlideRecord("cmdb_ci"); gr.addQuery("asset_tag", assetTag); gr.setLimit(1); gr.query(); if (gr.next()) { return JSON.stringify({ found: true, ci_sys_id: gr.getUniqueValue(), ci_name: gr.getDisplayValue("name"), ci_class: gr.getDisplayValue("sys_class_name") }); } return JSON.stringify({ found: false }); }, type: "AssetValidator" }); ``` ### Script Include -- Multi-Method Pattern ```javascript var CatalogUtils = Class.create(); CatalogUtils.prototype = Object.extendsObject(global.AbstractAjaxProcessor, { getItemPrice: function () { var itemId = this.getParameter("sysparm_item_id"); var gr = new GlideRecord("sc_cat_item"); if (gr.get(itemId)) { return gr.getValue("price"); } return "0"; }, getManagerName: function () { var userId = this.getParameter("sysparm_user_id"); var gr = new GlideRecord("sys_user"); if (gr.get(userId)) { return JSON.stringify({ manager_sys_id: gr.getValue("manager"), manager_name: gr.getDisplayValue("manager"), department: gr.getDisplayValue("department") }); } return JSON.stringify({ error: "User not found" }); }, type: "CatalogUtils" }); ``` ### Script Include -- Input Validation ```javascript getUserInfo: function() { var userId = this.getParameter('sysparm_user_id'); // Validate: check it looks like a sys_id if (!userId || userId.length !== 32) { return JSON.stringify({ error: 'Invalid user ID' }); } var gr = new GlideRecord('sys_user'); if (gr.get(userId)) { return JSON.stringify({ name: gr.getDisplayValue('name') }); } return JSON.stringify({ error: 'User not found' }); } ```