Client Script Use-case Scenario Questions
- How would you use a Client Script to validate user input in a form field?
- Can you explain how to dynamically hide or show form fields based on user selections using a Client Script?
- Describe a scenario where you would use a Client Script to auto-populate a field based on another field's value.
- How do you prevent users from submitting a form until certain conditions are met using Client Scripts?
- Explain how to use a Client Script to perform client-side validation for a date field to ensure it's in the future.
- Provide an example of using a Client Script to display a confirmation dialog before submitting a form.
- Describe a scenario where you would use a Client Script to restrict certain users from editing specific fields.
- How would you implement a Client Script to enforce character limits in a text area field?
- Explain how to use Client Scripts to dynamically set the value of a choice field based on a user's role.
- Describe a scenario where you would use a Client Script to automatically calculate the total cost based on item quantity and price.
- Can you provide an example of using a Client Script to display a custom error message when certain conditions are met?
- How would you use a Client Script to automatically update the priority field based on the selected category?
- Explain how to use a Client Script to prevent users from adding attachments larger than a specified size.
- Describe a scenario where you would use a Client Script to dynamically change the background color of a form field based on its value.
- Provide an example of using a Client Script to validate email addresses entered in a form field.
- How do you use a Client Script to enforce mandatory fields before submitting a form?
- Describe a scenario where you would use a Client Script to perform an AJAX call to retrieve additional data for a form field.
- Explain how to use Client Scripts to disable form fields based on certain conditions.
- How would you implement a Client Script to display a warning message when a certain date is approaching?
- Provide an example of using a Client Script to restrict access to a form based on the user's department.
- Describe a scenario where you would use a Client Script to validate phone numbers entered in a form field.
- Can you explain how to use a Client Script to automatically populate location information based on GPS coordinates?
- How do you use Client Scripts to dynamically update the options available in a choice field based on another field's value?
- Describe a scenario where you would use a Client Script to display a warning when a form has been idle for too long.
- Explain how to use Client Scripts to validate numeric input within a specified range.
- Provide an example of using a Client Script to hide certain form sections based on user roles.
- How would you implement a Client Script to display a progress indicator while submitting a form?
- Describe a scenario where you would use a Client Script to validate URL formats entered in a form field.
- Can you explain how to use a Client Script to automatically calculate due dates based on selected options?
- Explain how to use Client Scripts to prevent users from submitting duplicate records.
- Describe a scenario where you would use a Client Script to perform real-time validation of a credit card number.
- Provide an example of using a Client Script to auto-populate user information based on the logged-in user.
- How do you use Client Scripts to dynamically update the available choices in a dependent choice field?
- Describe a scenario where you would use a Client Script to display tooltips for form fields.
- Explain how to use Client Scripts to restrict past dates from being selected in a date field.
- Provide an example of using a Client Script to limit the number of characters entered in a text field.
- How would you implement a Client Script to automatically populate form fields based on data from an external source?
- Describe a scenario where you would use a Client Script to validate input against a predefined list of values.
- Can you explain how to use a Client Script to dynamically adjust form field visibility based on screen size?
- Explain how to use Client Scripts to prevent users from submitting forms outside of business hours.
- Describe a scenario where you would use a Client Script to validate special characters entered in a form field.
- Provide an example of using a Client Script to enforce unique values in a form field.
- How do you use Client Scripts to display notifications for users when specific conditions are met?
- Describe a scenario where you would use a Client Script to validate alphanumeric input in a form field.
- Can you explain how to use a Client Script to autofill in form fields based on the selected user?
- Explain how to use Client Scripts to prevent users from submitting forms with invalid file formats in attachments.
- Describe a scenario where you would use a Client Script to validate user input against a regular expression pattern.
- Provide an example of using a Client Script to dynamically adjust form field labels based on user selections.
- How do you use Client Scripts to display contextual help text for form fields?
- Describe a scenario where you would use a Client Script to automatically update related records based on form input.
- Explain how to use Client Scripts to calculate and display the age based on a selected birthdate.
- Provide an example of using a Client Script to prevent users from submitting forms if certain conditions are not met.
- How would you implement a Client Script to automatically capitalize the first letter of input in a text field?
- Describe a scenario where you would use a Client Script to validate checkbox selections in a form.
1. Validate User Input
Scenario: Ensure a specific text field (u_serial_number
) only contains alphanumeric characters.
Solution: Use an onChange
Client Script on the u_serial_number
field.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
return;
}
// Regular expression for alphanumeric characters only
var pattern = /^[a-zA-Z0-9]+$/;
if (!pattern.test(newValue)) {
// Clear the invalid value (optional)
// g_form.setValue('u_serial_number', '');
// Show an error message
g_form.showFieldMsg('u_serial_number', 'Serial number must be alphanumeric.', 'error');
} else {
// Clear any previous error message if input is now valid
g_form.hideFieldMsg('u_serial_number', true);
}
}
-
Type:
onChange
-
Field:
u_serial_number
-
Explanation: This script triggers whenever the
u_serial_number
field changes. It uses a regular expression (pattern
) to test if thenewValue
contains only letters and numbers. If not, it displays an error message usingg_form.showFieldMsg()
.
2. Dynamically Hide/Show Fields
Scenario: Show a "Reason for Rejection" field (u_rejection_reason
) only when the "Status" field (state
) is set to "Rejected".
Solution: Use an onChange
Client Script on the state
field. (Note: A UI Policy is often preferred for visibility rules, but a Client Script can also achieve this).
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) {
// Optionally hide the field on load if the initial state isn't 'Rejected'
// Check the initial value and hide if necessary
var initialState = g_form.getValue('state');
// Assuming 'Rejected' has a value of '7'
if(initialState !== '7') {
g_form.setVisible('u_rejection_reason', false);
}
return;
}
// Assuming the value for 'Rejected' state is '7'
var rejectedValue = '7';
if (newValue === rejectedValue) {
g_form.setVisible('u_rejection_reason', true);
g_form.setMandatory('u_rejection_reason', true); // Make it mandatory when visible
} else {
g_form.setVisible('u_rejection_reason', false);
g_form.setMandatory('u_rejection_reason', false); // Make it non-mandatory when hidden
g_form.setValue('u_rejection_reason', ''); // Clear the value when hidden
}
}
-
Type:
onChange
-
Field:
state
-
Explanation: When the
state
field changes, the script checks if thenewValue
matches the value representing "Rejected". If it does, theu_rejection_reason
field is made visible and mandatory. Otherwise, it's hidden, made non-mandatory, and cleared.
3. Auto-populate Field Based on Another
Scenario: When a user is selected in the caller_id
field, automatically populate the u_caller_email
and u_caller_phone
fields with the selected user's email and phone number.
Solution: Use an onChange
Client Script on the caller_id
field.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
// Clear fields if caller is cleared
if (newValue === '') {
g_form.setValue('u_caller_email', '');
g_form.setValue('u_caller_phone', '');
}
return;
}
// Get the reference record for the selected caller
var caller = g_form.getReference('caller_id', function(callerRecord) {
// This callback function executes once the reference record data is available
if (callerRecord) {
g_form.setValue('u_caller_email', callerRecord.email);
g_form.setValue('u_caller_phone', callerRecord.phone || callerRecord.mobile_phone); // Use phone or mobile
}
});
}
-
Type:
onChange
-
Field:
caller_id
(Reference field to User table) -
Explanation: This script triggers when the
caller_id
changes. It usesg_form.getReference()
with a callback function to asynchronously retrieve the selected user's record. Once the record (callerRecord
) is retrieved, it populates the email and phone fields using the data from that record.
4. Prevent Form Submission Until Conditions Met
Scenario: Prevent submitting an Incident form if the assignment_group
is empty when the state
is "In Progress".
Solution: Use an onSubmit
Client Script.
function onSubmit() {
// Get current values
var state = g_form.getValue('state');
var assignmentGroup = g_form.getValue('assignment_group');
// Define the condition values ('In Progress' might be '2', check your instance)
var inProgressState = '2';
// Check if state is 'In Progress' and assignment group is empty
if (state == inProgressState && assignmentGroup == '') {
// Display an error message to the user
g_form.addErrorMessage('Assignment group cannot be empty when the state is In Progress.');
// Prevent the form submission
return false;
}
// If conditions are met or don't apply, allow submission
return true;
}
-
Type:
onSubmit
-
Explanation: This script runs just before the form is submitted. It checks if the
state
is "In Progress" and theassignment_group
field is empty. If both conditions are true, it displays an error message usingg_form.addErrorMessage()
and returnsfalse
to halt the submission process. Otherwise, it returnstrue
, allowing the submission.
5. Validate Date Field is in the Future
Scenario: Ensure the "Planned End Date" (end_date
) selected by the user is later than the current date and time.
Solution: Use an onChange
Client Script on the end_date
field.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
return;
}
var selectedDate = new Date(newValue);
var currentDate = new Date();
console.log("selected date: "+ selectedDate);
console.log("Current date: "+ currentDate);
if (selectedDate <= currentDate){
g_form.setValue('u_end_date','');
g_form.showFieldMsg('u_end_date','Please select future date','error');
}else{
g_form.hideFieldMsg('u_end_date',true);
}
//Type appropriate comment here, and begin script below
}
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
return;
}
// Clear previous messages
g_form.hideFieldMsg('end_date', true);
// Get the selected date value (ServiceNow stores dates internally in UTC)
// g_form.getValue() returns the date in user's format, need GlideAjax for reliable comparison or JS Date object parsing
// Simpler approach using JS Date object (may have timezone caveats depending on exact need)
var selectedDate = new Date(getDateFromFormat(newValue, g_user_date_time_format)); // Parse user date string
var now = new Date(); // Current date and time
// Set hours, minutes, seconds, and milliseconds to 0 for 'now' if comparing only dates
// now.setHours(0,0,0,0);
// Do the same for selectedDate if needed
if (selectedDate <= now) {
g_form.setValue('end_date', ''); // Clear the invalid date
g_form.showFieldMsg('end_date', 'Planned End Date must be in the future.', 'error');
}
}
// Helper function (often needed, ServiceNow doesn't provide it directly client-side)
// Note: ServiceNow now has GlideDate/GlideDateTime APIs usable client-side in newer versions, which are better.
// For older versions or simplicity here, getDateFromFormat is assumed.
// Ensure getDateFromFormat is defined, perhaps in a UI Script loaded globally.
// Example placeholder:
// function getDateFromFormat(dateStr, format) { /* Complex parsing logic here */ return dateStr; /* Placeholder only */ }
-
Type:
onChange
-
Field:
end_date
-
Explanation: When the
end_date
changes, the script parses the selected date string into a JavaScriptDate
object (handling user date formats is crucial here, potentially requiringGlideAjax
or newer client-side date APIs for robustness). It compares this date with the current date/time. If the selected date is not in the future, it clears the field and shows an error message. Note: Accurate date/time comparison client-side, especially across timezones, can be tricky. Consider server-side validation for critical checks.
6. Display Confirmation Dialog Before Submission
Scenario: Before submitting a "Change Request" form, ask the user to confirm if they have attached all necessary documentation.
Solution: Use an onSubmit
Client Script.
function onSubmit() {
// Condition to check (e.g., only ask for specific change types)
var changeType = g_form.getValue('type');
if (changeType == 'emergency') { // Only ask for emergency changes
// Display a confirmation dialog
var confirmation = confirm("Have you attached all necessary documentation for this emergency change?");
// If the user clicks 'Cancel' (confirmation is false), prevent submission
if (!confirmation) {
// Optional: Add a message indicating why submission was cancelled
// g_form.addInfoMessage('Submission cancelled. Please attach required documentation.');
return false;
}
}
// If user confirms or the condition doesn't apply, allow submission
return true;
}
-
Type:
onSubmit
-
Explanation: This script runs before submission. It checks if the change type is "emergency". If so, it uses the standard JavaScript
confirm()
function to display a pop-up dialog. If the user clicks "Cancel" (confirm()
returnsfalse
), the script returnsfalse
, stopping the submission. Otherwise (confirm()
returnstrue
), the script returnstrue
, allowing submission.
7. Restrict Field Editing Based on User Role
Scenario: Prevent users without the itil_admin
role from editing the priority
field on an Incident form once it's saved (i.e., not a new record).
Solution: Use an onLoad
Client Script. (Note: ACLs are the secure way to control field write access. Client Scripts only provide UI control and can be bypassed).
function onLoad() {
// Check if the user has the 'itil_admin' role
var isAdmin = g_user.hasRole('itil_admin');
// Check if it's not a new record
var isNewRecord = g_form.isNewRecord();
// If the user is NOT an admin AND it's an existing record
if (!isAdmin && !isNewRecord) {
// Make the 'priority' field read-only
g_form.setReadOnly('priority', true);
}
}
-
Type:
onLoad
-
Explanation: This script runs when the form loads. It checks if the current user (
g_user
) has theitil_admin
role and if the record is not new (!g_form.isNewRecord()
). If the user lacks the role and the record already exists, it makes thepriority
field read-only usingg_form.setReadOnly()
. Warning: This is cosmetic; use Access Controls (ACLs) for true security.
8. Enforce Character Limits in Text Area
Scenario: Limit the input in the comments
(Journal Input) field or a multi-line text field (u_detailed_description
) to 500 characters.
Solution: Use an onChange
Client Script on the field.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) {
return;
}
var fieldName = 'u_detailed_description'; // Or the specific field name
var maxLength = 500;
// Clear previous messages
g_form.hideFieldMsg(fieldName, true);
if (newValue.length > maxLength) {
// Display an error message
g_form.showFieldMsg(fieldName, 'Input cannot exceed ' + maxLength + ' characters. Current length: ' + newValue.length, 'error');
// Optionally truncate the text (can be abrupt for users)
// g_form.setValue(fieldName, newValue.substring(0, maxLength));
}
}
// Note: For Journal fields like 'comments', direct manipulation is limited.
// You might need to use an onSubmit script instead or hook into Journal field events if available.
// For standard multi-line text fields, onChange works well.
-
Type:
onChange
-
Field:
u_detailed_description
(or other text area) -
Explanation: This script triggers when the specified text area field changes. It checks the
length
of thenewValue
. If it exceeds themaxLength
, it displays an error message indicating the limit and the current length. Optionally, it could truncate the input, but showing a message is usually better UX.
9. Dynamically Set Choice Field Value Based on Role
Scenario: When a form loads, automatically set the "Impact" (impact
) field to "1 - High" if the user has the major_incident_manager
role.
Solution: Use an onLoad
Client Script.
function onLoad() {
// Check if it's a new record to avoid overwriting existing values unintentionally
if (!g_form.isNewRecord()) {
return;
}
// Check if the user has the specific role
var hasRole = g_user.hasRole('major_incident_manager');
if (hasRole) {
// Set the 'impact' field value to '1' (assuming '1' is the value for High impact)
g_form.setValue('impact', '1');
// Optional: Add a message indicating why it was set
g_form.addInfoMessage('Impact automatically set to High based on your role.');
}
}
-
Type:
onLoad
-
Explanation: This script runs when the form loads. It first checks if the record is new. Then, it verifies if the current user (
g_user
) has themajor_incident_manager
role. If they do, it sets theimpact
field's value to1
(the assumed value for "High").
10. Automatically Calculate Total Cost
Scenario: On a Request Item form, automatically calculate the "Total Cost" (u_total_cost
) based on the "Quantity" (quantity
) and "Price Per Item" (u_price_per_item
) fields.
Solution: Use an onChange
Client Script that triggers when either quantity
or u_price_per_item
changes. You'll need two identical scripts, one for each field, or a single script called by both.
// Script for onChange of 'quantity'
function onChangeQuantity(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) return;
calculateTotalCost();
}
// Script for onChange of 'u_price_per_item'
function onChangePrice(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) return;
calculateTotalCost();
}
// Shared calculation function (could also be in a UI Script)
function calculateTotalCost() {
var quantity = g_form.getIntValue('quantity'); // Use getIntValue for calculations
var price = g_form.getDecimalValue('u_price_per_item'); // Use getDecimalValue for currency/decimals
// Check if both values are valid numbers
if (!isNaN(quantity) && !isNaN(price) && quantity >= 0 && price >= 0) {
var total = quantity * price;
// Set the value, ensuring it's formatted correctly (e.g., 2 decimal places)
// For currency fields, ServiceNow often handles formatting, just set the number.
g_form.setValue('u_total_cost', total.toFixed(2));
} else {
// If inputs are invalid or non-numeric, clear the total or set to 0
g_form.setValue('u_total_cost', 0);
}
}
// Also consider an onLoad script to calculate the total when the form first loads
function onLoad() {
calculateTotalCost();
}
-
Type:
onChange
(onquantity
andu_price_per_item
),onLoad
-
Fields:
quantity
,u_price_per_item
-
Explanation: Separate
onChange
scripts are created forquantity
andu_price_per_item
. Both call a common functioncalculateTotalCost
. This function retrieves the integer value of quantity and the decimal value of price, calculates the product, and sets theu_total_cost
field, formatting it to two decimal places. AnonLoad
script ensures the calculation runs when the form loads with existing values.
11. Display Custom Error Message
Scenario: If a user selects "Hardware" as the Category and "Laptop" as the Subcategory, but leaves the "Asset Tag" field empty, display a specific error message on the Asset Tag field.
Solution: Use an onChange
Client Script (potentially on Subcategory, assuming Category is selected first) or an onSubmit
script. Using onSubmit
ensures the check happens before saving.
// Example using onSubmit
function onSubmit() {
var category = g_form.getValue('category');
var subcategory = g_form.getValue('subcategory');
var assetTag = g_form.getValue('asset_tag'); // Assuming field name is asset_tag
// Check for the specific combination
if (category == 'hardware' && subcategory == 'laptop' && assetTag == '') {
// Show error message on the specific field
g_form.showFieldMsg('asset_tag', 'Asset Tag is required for Hardware/Laptop requests.', 'error');
// Prevent submission
return false;
}
// Clear message if condition no longer met (better in onChange, but can be done here too)
// g_form.hideFieldMsg('asset_tag', true); // May hide too eagerly in onSubmit
return true; // Allow submission
}
-
Type:
onSubmit
(oronChange
onsubcategory
andasset_tag
) -
Explanation: This
onSubmit
script checks if the Category is "Hardware", Subcategory is "Laptop", and Asset Tag is empty. If this specific condition is met, it usesg_form.showFieldMsg()
to display a targeted error message directly under theasset_tag
field and prevents submission by returningfalse
.
12. Auto-update Priority Based on Category
Scenario: When a user sets the "Category" (category
) field to "Network", automatically set the "Priority" (priority
) to "2 - High".
Solution: Use an onChange
Client Script on the category
field.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
return;
}
// Check if the new category value is 'network' (use the actual value)
if (newValue == 'network') {
// Set Priority to '2' (assuming '2' is the value for 'High')
g_form.setValue('priority', '2');
g_form.addInfoMessage('Priority set to High based on Network category.');
}
// Optional: Add else logic to handle other categories if needed
// else if (newValue == 'software') {
// g_form.setValue('priority', '3'); // Set to Medium for Software
// }
}
-
Type:
onChange
-
Field:
category
-
Explanation: When the
category
field changes, this script checks if thenewValue
corresponds to "Network". If it does, it automatically sets thepriority
field's value to2
(assuming this represents "High") and notifies the user.
13. Prevent Large Attachments (Client-Side Indication)
Scenario: Warn the user immediately if they attempt to attach a file larger than 5MB. Note: True enforcement must happen server-side.
Solution: This is tricky with standard Client Scripts as they don't have direct access to the file before upload starts in the default attachment UI. You often need to leverage specific Service Portal widget capabilities or DOM manipulation (which is discouraged and fragile). A more common approach is server-side validation (e.g., Business Rule on sys_attachment
) and potentially setting system properties (com.glide.attachment.max_size
).
However, if using a custom UI or Service Portal widget, you could use JavaScript's File API:
// This code would typically be part of a custom UI element/widget's script, NOT a standard Platform UI Client Script.
// Assuming 'fileInput' is an HTML file input element <input type="file" id="fileInput">
var fileInput = document.getElementById('fileInput');
fileInput.onchange = function() {
if (this.files.length > 0) {
var file = this.files[0];
var maxSizeMB = 5;
var maxSizeBytes = maxSizeMB * 1024 * 1024;
if (file.size > maxSizeBytes) {
alert('Error: File size exceeds the maximum limit of ' + maxSizeMB + 'MB.');
// Clear the file input to prevent upload
this.value = null;
} else {
// Proceed with attaching/uploading the file
console.log('File size is acceptable.');
}
}
};
- Type: Not a standard Platform UI Client Script. Requires custom UI/Widget scripting.
-
Explanation: This JavaScript example uses the HTML File API. When a file is selected in the
fileInput
element, it checks thefile.size
against themaxSizeBytes
. If it's too large, an alert is shown, and the input is cleared. Crucially, this does not work reliably in the standard Platform UI's attachment pop-up without significant customization or unsupported DOM manipulation. Server-side validation is the reliable method.
14. Dynamically Change Field Background Color
Scenario: Change the background color of the "Risk" (risk
) field to red if its value is "High".
Solution: Use an onChange
Client Script on the risk
field. (Note: Direct style manipulation can be inconsistent across UI versions and less maintainable than using field styles).
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) {
return; // Don't run on load unless needed, handle in onLoad separately
}
var fieldName = 'risk';
var highRiskValue = '1'; // Assuming '1' is the value for 'High' risk
try {
// Get the HTML element for the field's control
// This relies on predictable DOM structure, which can change between ServiceNow versions!
var controlElement = g_form.getControl(fieldName);
if (controlElement) {
if (newValue == highRiskValue) {
// Apply red background
controlElement.style.backgroundColor = '#FFA0A0'; // Light red
} else {
// Reset to default background
controlElement.style.backgroundColor = '';
}
}
} catch (e) {
jslog('Error changing background color for ' + fieldName + ': ' + e);
}
}
// Also recommended: an onLoad script to set the color initially
function onLoad() {
var fieldName = 'risk';
var highRiskValue = '1';
var currentValue = g_form.getValue(fieldName);
try {
var controlElement = g_form.getControl(fieldName);
if (controlElement && currentValue == highRiskValue) {
controlElement.style.backgroundColor = '#FFA0A0'; // Light red
}
} catch (e) {
jslog('Error setting initial background color for ' + fieldName + ': ' + e);
}
}
-
Type:
onChange
,onLoad
-
Field:
risk
-
Explanation: When the
risk
field changes, the script gets the field's HTML control usingg_form.getControl()
. If the new value represents "High", it sets thebackgroundColor
style of the element to light red. Otherwise, it resets the background color. AnonLoad
script handles the initial state. Caution: DOM manipulation is fragile. Using ServiceNow Field Styles is the recommended, more robust approach for styling based on value.
15. Validate Email Addresses
Scenario: Validate that the input in the u_contact_email
field follows a standard email format.
Solution: Use an onChange
Client Script on the u_contact_email
field.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
g_form.hideFieldMsg('u_contact_email', true); // Clear message if empty
return;
}
var fieldName = 'u_contact_email';
// Simple regex for basic email format check (can be more complex)
var emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(newValue)) {
// Show error message
g_form.showFieldMsg(fieldName, 'Please enter a valid email address format (e.g., user@example.com).', 'error');
} else {
// Clear any previous error message
g_form.hideFieldMsg(fieldName, true);
}
}
-
Type:
onChange
-
Field:
u_contact_email
-
Explanation: This script triggers when the email field changes. It uses a regular expression (
emailPattern
) to test if thenewValue
roughly matches a standard email structure. If the format is invalid, it displays an error message usingg_form.showFieldMsg()
; otherwise, it clears any existing message.
16. Enforce Mandatory Fields Before Submission
Scenario: Ensure that both the short_description
and description
fields are filled before an Incident form can be submitted. (Note: UI Policies are often better for simple mandatory rules).
Solution: Use an onSubmit
Client Script.
function onSubmit() {
// Assume fields start as non-mandatory, otherwise this check isn't needed
// Or, use this to enforce conditional mandatory fields
var shortDesc = g_form.getValue('short_description');
var desc = g_form.getValue('description');
var missingFields = [];
// Check if short description is empty
if (shortDesc.trim() === '') {
g_form.setMandatory('short_description', true); // Highlight the field
missingFields.push('Short description');
} else {
g_form.setMandatory('short_description', false); // Remove mandatory if filled (optional)
}
// Check if description is empty
if (desc.trim() === '') {
g_form.setMandatory('description', true); // Highlight the field
missingFields.push('Description');
} else {
g_form.setMandatory('description', false); // Remove mandatory if filled (optional)
}
// If any fields are missing
if (missingFields.length > 0) {
// Display a single error message listing missing fields
g_form.addErrorMessage('Please fill in the following mandatory fields: ' + missingFields.join(', '));
return false; // Prevent submission
}
// All checks passed, allow submission
return true;
}
-
Type:
onSubmit
-
Explanation: This script runs before submission. It checks if
short_description
ordescription
are empty (usingtrim()
to ignore whitespace). If a field is empty, it's marked as mandatory (which usually highlights it) and added to a list. If the list of missing fields is not empty at the end, a summary error message is shown, and submission is blocked (return false
). Note: For static mandatory fields, configuring them via the dictionary or a UI Policy is preferred. This script is more useful for conditional mandatory logic checked at submission time.
17. Perform AJAX Call for Additional Data
Scenario: When a Configuration Item (cmdb_ci
) is selected, use GlideAjax to call a Script Include that retrieves the CI's support group (support_group
) and business criticality (business_criticality
) and populates corresponding form fields (u_ci_support_group
, u_ci_criticality
).
Solution:
-
Create a Script Include:
- Name:
AjaxClientUtils
(or similar) - Client callable:
true
- Script:
var AjaxClientUtils = Class.create(); AjaxClientUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, { getCIDetails: function() { var ciSysId = this.getParameter('sysparm_ci_sysid'); var result = { support_group: '', business_criticality: '' }; if (!ciSysId) { return JSON.stringify(result); } var ciGr = new GlideRecord('cmdb_ci'); if (ciGr.get(ciSysId)) { result.support_group = ciGr.getValue('support_group'); result.business_criticality = ciGr.getValue('business_criticality'); // Use getDisplayValue() if you need the display name of the support group // result.support_group_display = ciGr.getDisplayValue('support_group'); } return JSON.stringify(result); }, type: 'AjaxClientUtils' });
- Name:
-
Create an
onChange
Client Script oncmdb_ci
:function onChange(control, oldValue, newValue, isLoading, isTemplate) { if (isLoading || newValue === '') { // Clear related fields if CI is cleared if (newValue === ''){ g_form.setValue('u_ci_support_group', ''); g_form.setValue('u_ci_criticality', ''); } return; } // Create a GlideAjax instance pointing to the Script Include var ga = new GlideAjax('AjaxClientUtils'); // Specify the function name to call in the Script Include ga.addParam('sysparm_name', 'getCIDetails'); // Pass the selected CI's sys_id as a parameter ga.addParam('sysparm_ci_sysid', newValue); // Send the request asynchronously and define the callback function ga.getXML(handleResponse); } // Callback function to process the response from the server function handleResponse(response) { // Extract the answer element from the XML response var answer = response.responseXML.documentElement.getAttribute("answer"); // Clear fields before setting new values potentially g_form.setValue('u_ci_support_group', ''); g_form.setValue('u_ci_criticality', ''); if (answer) { try { // Parse the JSON string returned from the Script Include var ciDetails = JSON.parse(answer); // Populate the form fields with the retrieved data // Use ciDetails.support_group_display if you retrieved display value g_form.setValue('u_ci_support_group', ciDetails.support_group); g_form.setValue('u_ci_criticality', ciDetails.business_criticality); } catch (e) { jslog('Error parsing CI details response: ' + e); // Handle potential JSON parsing errors } } }
-
Type:
onChange
(Client Script), Script Include (Server-side) -
Field:
cmdb_ci
-
Explanation: When the
cmdb_ci
field changes, the Client Script initiates aGlideAjax
call to theAjaxClientUtils
Script Include, passing the selected CI's sys_id. The Script Include (getCIDetails
function) runs server-side, queries thecmdb_ci
table for the given sys_id, retrieves thesupport_group
andbusiness_criticality
, and returns them as a JSON string. The Client Script's callback function (handleResponse
) receives this JSON, parses it, and usesg_form.setValue()
to populate the corresponding fields on the form.
18. Disable Form Fields Based on Conditions
Scenario: Disable the assignment_group
and assigned_to
fields if the "State" (state
) is set to "Closed" or "Cancelled". (Note: UI Policies are often better for disabling fields).
Solution: Use an onChange
Client Script on the state
field.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
// Define the state values for Closed and Cancelled (check your instance's values)
var closedState = '7';
var cancelledState = '8';
var shouldDisable = (newValue == closedState || newValue == cancelledState);
// Disable/Enable Assignment group
g_form.setDisabled('assignment_group', shouldDisable);
// Disable/Enable Assigned to
g_form.setDisabled('assigned_to', shouldDisable);
// If disabling, you might want to clear the values (optional)
// if (shouldDisable) {
// g_form.setValue('assignment_group', '');
// g_form.setValue('assigned_to', '');
// }
}
// Also add an onLoad script to set the initial state
function onLoad() {
var closedState = '7';
var cancelledState = '8';
var currentState = g_form.getValue('state');
var shouldDisable = (currentState == closedState || currentState == cancelledState);
g_form.setDisabled('assignment_group', shouldDisable);
g_form.setDisabled('assigned_to', shouldDisable);
}
-
Type:
onChange
,onLoad
-
Field:
state
-
Explanation: The
onChange
script checks if thenewValue
of thestate
field matches the values for "Closed" or "Cancelled". If it does, it sets theassignment_group
andassigned_to
fields to disabled usingg_form.setDisabled(fieldName, true)
. Otherwise, it enables them (g_form.setDisabled(fieldName, false)
). TheonLoad
script ensures the fields are correctly disabled/enabled when the form initially loads based on the current state.
19. Display Warning Message for Approaching Date
Scenario: If a "Follow Up" date (follow_up
) is set, display an info message on the form if that date is within the next 3 days.
Solution: Use an onLoad
and onChange
Client Script.
// Function to be called by onLoad and onChange
function checkFollowUpDate() {
var fieldName = 'follow_up';
var followUpDateStr = g_form.getValue(fieldName);
// Clear previous messages first
g_form.hideFieldMsg(fieldName, true); // Clears field message
// Clear form messages (use a unique key if adding multiple form messages)
// g_form.clearMessages(); // Use cautiously, clears all messages
if (followUpDateStr === '') {
return; // No date set, nothing to do
}
try {
// Attempt to parse the date - Requires robust parsing or GlideAjax/newer client date APIs
var followUpDate = new Date(getDateFromFormat(followUpDateStr, g_user_date_time_format)); // Assumes helper function
var now = new Date();
var threeDaysLater = new Date();
threeDaysLater.setDate(now.getDate() + 3);
// Normalize dates to compare day only (optional, depends on requirement)
// now.setHours(0,0,0,0);
// followUpDate.setHours(0,0,0,0);
// threeDaysLater.setHours(0,0,0,0);
if (followUpDate > now && followUpDate <= threeDaysLater) {
// Display warning message near the field
g_form.showFieldMsg(fieldName, 'Follow up date is approaching soon (within 3 days).', 'info');
// OR display a general info message at the top
// g_form.addInfoMessage('Reminder: Follow up date (' + followUpDateStr + ') is approaching soon.');
}
} catch(e) {
jslog('Error parsing follow up date: ' + e);
}
}
// onLoad Script
function onLoad() {
checkFollowUpDate();
}
// onChange Script for 'follow_up' field
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) return;
checkFollowUpDate();
}
// Assume getDateFromFormat exists (see Q5 explanation)
-
Type:
onLoad
,onChange
-
Field:
follow_up
-
Explanation: A common function
checkFollowUpDate
is created. It retrieves thefollow_up
date, parses it (this part needs care with date formats), calculates the date 3 days from now, and compares. If the follow-up date is between now and 3 days from now, it displays an informational message usingg_form.showFieldMsg()
org_form.addInfoMessage()
. This function is called by anonLoad
script to check when the form loads and anonChange
script to check whenever the date is modified.
20. Restrict Form Access Based on Department (UI Only)
Scenario: Prevent users not in the "Finance" department from seeing the contents of a specific sensitive form (e.g., a "Financial Request" form) when they open it. Show a message and potentially redirect. (Note: Secure access control requires ACLs).
Solution: Use an onLoad
Client Script.
function onLoad() {
// Get the user's department sys_id
// Requires dot-walking enabled for g_user or a GlideAjax call
// Simple approach (if user record has department cached, not always reliable):
// var userDepartment = g_user.department; // This might be sys_id
// More reliable: Use GlideAjax to get department info server-side
var ga = new GlideAjax('AjaxClientUtils'); // Assuming a utility script include
ga.addParam('sysparm_name','getUserDepartment');
ga.addParam('sysparm_user_id', g_user.userID);
ga.getXML(handleDepartmentResponse);
}
function handleDepartmentResponse(response) {
var answer = response.responseXML.documentElement.getAttribute("answer");
// Assume answer returns the sys_id of the user's department
var userDepartmentSysId = answer;
// Sys_id for the 'Finance' department (replace with actual sys_id)
var financeDeptSysId = 'abcdef1234567890abcdef1234567890';
if (userDepartmentSysId !== financeDeptSysId) {
// User is not in Finance
// 1. Clear the form (optional, prevents flashing data)
// g_form.clearValues(); // Use with caution
// 2. Hide all form sections (makes form appear blank)
// Note: Hiding sections might be complex depending on form layout
var sections = g_form.getSections();
for(var i=0; i < sections.length; i++){
g_form.setSectionDisplay(sections[i].getAttribute('id'), false); // Might need different way to get section name/id
}
// Alternatively hide all fields (less clean)
// g_form.elements.forEach(function(field){ g_form.setVisible(field.fieldName, false); });
// 3. Display a message
g_form.addErrorMessage('You do not have permission to view this form based on your department.');
// 4. Optionally disable all fields (prevents interaction)
g_form.disableAttachments();
g_form.setReadOnly(true); // Makes all fields read-only
// 5. Optionally redirect (e.g., back to list or portal) - use with care
// window.location.href = 'nav_to.do?uri=/incident_list.do'; // Example redirect
}
}
// Add 'getUserDepartment' function to your AjaxClientUtils Script Include:
/*
getUserDepartment: function() {
var userId = this.getParameter('sysparm_user_id');
var userGr = new GlideRecord('sys_user');
if (userGr.get(userId)) {
return userGr.getValue('department');
}
return '';
},
*/
-
Type:
onLoad
(+ GlideAjax + Script Include) -
Explanation: This
onLoad
script usesGlideAjax
to securely fetch the user's department sys_id from the server. It compares the user's department sys_id to the known sys_id of the "Finance" department. If they don't match, it takes actions to obscure the form: hiding sections/fields, displaying an error message, and making the form read-only. Crucially, this is UI obfuscation only. Use Access Controls (ACLs) based on department for proper security to prevent data access.
21. Validate Phone Numbers
Scenario: Ensure a u_contact_phone
field contains a reasonably formatted phone number (e.g., allows digits, spaces, hyphens, parentheses, optional +).
Solution: Use an onChange
Client Script.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
g_form.hideFieldMsg('u_contact_phone', true); // Clear message if empty
return;
}
var fieldName = 'u_contact_phone';
// Regex allowing digits, spaces, hyphens, parentheses, and an optional leading +
// Adjust complexity based on required formats (e.g., minimum digits)
var phonePattern = /^\+?[\d\s\-()]+$/;
// Example for minimum 7 digits: /^\+?[\d\s\-()]{7,}$/
// More specific North American format: /^\+?1?[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/
if (!phonePattern.test(newValue)) {
// Show error message
g_form.showFieldMsg(fieldName, 'Please enter a valid phone number format.', 'error');
} else {
// Clear any previous error message
g_form.hideFieldMsg(fieldName, true);
}
}
-
Type:
onChange
-
Field:
u_contact_phone
-
Explanation: When the phone field changes, this script uses a regular expression (
phonePattern
) to check if the input consists of allowed characters (digits, spaces, hyphens, parentheses, optional +). If the pattern doesn't match, an error message is displayed. You can adjust the regex for stricter format enforcement if needed.
22. Auto-populate Location from GPS (Conceptual)
Scenario: If a form has fields for latitude (u_latitude
) and longitude (u_longitude
), automatically populate a "Location" reference field (location
) by finding the nearest location record based on those coordinates.
Solution: This requires asynchronous processing and likely server-side logic for the geo-lookup.
-
Client Script (
onChange
onu_latitude
andu_longitude
): Trigger an AJAX call when coordinates change (after some debounce potentially). -
Script Include (Called via GlideAjax): Receives lat/lon, queries the Location table (
cmn_location
), calculates distances (complex, might need external library or database function if available), finds the nearest match, and returns the sys_id. -
Client Script (Callback): Receives the sys_id and populates the
location
field.
// onChange Client Script (simplified - apply to both lat/lon fields)
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') return;
lookupLocationByCoords();
}
function lookupLocationByCoords(){
var lat = g_form.getValue('u_latitude');
var lon = g_form.getValue('u_longitude');
// Basic check if both have values
if (lat && lon) {
var ga = new GlideAjax('LocationUtils'); // New Script Include name
ga.addParam('sysparm_name', 'findNearestLocation');
ga.addParam('sysparm_lat', lat);
ga.addParam('sysparm_lon', lon);
ga.getXML(handleLocationResponse);
}
}
function handleLocationResponse(response) {
var answer = response.responseXML.documentElement.getAttribute("answer");
var locationSysId = answer;
if (locationSysId) {
g_form.setValue('location', locationSysId);
g_form.addInfoMessage('Location field populated based on coordinates.');
} else {
g_form.setValue('location', ''); // Clear if no location found
// Optional: g_form.showFieldMsg('location', 'Could not find a matching location for the coordinates.', 'info');
}
}
// --- Server-Side Script Include: LocationUtils ---
/*
var LocationUtils = Class.create();
LocationUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
findNearestLocation: function() {
var lat = this.getParameter('sysparm_lat');
var lon = this.getParameter('sysparm_lon');
var nearestLocationSysId = '';
var minDistance = Infinity; // Initialize with a very large distance
// --- !!! IMPORTANT !!! ---
// Calculating distance between coordinates and querying efficiently
// based on proximity is COMPLEX. Simple GlideRecord iteration is
// highly inefficient for large location tables.
// This requires either:
// 1. A geospatial database plugin/feature (if ServiceNow instance has one).
// 2. An external Geocoding/Reverse Geocoding service API call.
// 3. A simplified approach for small datasets (like bounding box query first).
// The example below is PSEUDO-CODE for the concept and likely INEFFICIENT.
var locGr = new GlideRecord('cmn_location');
locGr.addNotNullQuery('latitude'); // Only consider locations with coordinates
locGr.addNotNullQuery('longitude');
locGr.query();
while (locGr.next()) {
var locLat = locGr.getValue('latitude');
var locLon = locGr.getValue('longitude');
// Calculate distance (e.g., using Haversine formula - needs implementation)
var distance = this.calculateHaversineDistance(lat, lon, locLat, locLon);
if (distance < minDistance) {
minDistance = distance;
nearestLocationSysId = locGr.getUniqueValue();
}
}
// Optional: Add a threshold - only return if distance is below X km/miles
// if (minDistance > SOME_THRESHOLD) {
// return '';
// }
return nearestLocationSysId;
},
// Placeholder for Haversine distance calculation function
calculateHaversineDistance: function(lat1, lon1, lat2, lon2) {
// ... implementation of Haversine formula ...
// Returns distance in kilometers or miles
return 0; // Placeholder
},
type: 'LocationUtils'
});
*/
-
Type:
onChange
(Client Script), GlideAjax, Script Include -
Fields:
u_latitude
,u_longitude
,location
-
Explanation: Client Scripts on the coordinate fields trigger a
GlideAjax
call. The server-side Script Include receives the coordinates. It should then perform an efficient geospatial query against thecmn_location
table to find the location record with the closest latitude/longitude. The sys_id of the nearest location is returned to the client script, which populates thelocation
reference field. Note: The server-side distance calculation and querying part is non-trivial and performance-sensitive.
23. Dynamically Update Choice Field Options
Scenario: Have two choice fields, "Category" (category
) and "Subcategory" (subcategory
). The options shown in "Subcategory" should depend on the value selected in "Category". (Note: Dependent fields feature is the standard way to achieve this).
Solution: Use an onChange
Client Script on the category
field.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) {
return;
}
// Clear existing options and the current value in subcategory
g_form.clearOptions('subcategory');
g_form.setValue('subcategory', ''); // Clear selected value
// Add a default '-- None --' option
g_form.addOption('subcategory', '', '-- None --');
// Add options based on the selected category
if (newValue == 'hardware') { // Use actual category values
g_form.addOption('subcategory', 'laptop', 'Laptop');
g_form.addOption('subcategory', 'monitor', 'Monitor');
g_form.addOption('subcategory', 'keyboard', 'Keyboard');
} else if (newValue == 'software') {
g_form.addOption('subcategory', 'os', 'Operating System');
g_form.addOption('subcategory', 'app', 'Application Software');
g_form.addOption('subcategory', 'db', 'Database');
} else if (newValue == 'network') {
g_form.addOption('subcategory', 'wifi', 'Wireless');
g_form.addOption('subcategory', 'lan', 'Wired LAN');
g_form.addOption('subcategory', 'vpn', 'VPN Access');
}
// Add more conditions as needed
}
// It's also good practice to have an onLoad script to set the initial
// subcategory options based on the category value when the form loads.
// The onLoad script would contain logic similar to the onChange.
-
Type:
onChange
(oncategory
), potentiallyonLoad
-
Fields:
category
,subcategory
-
Explanation: When the
category
field changes, the script first clears all existing options from thesubcategory
field usingg_form.clearOptions()
. It then adds a default "-- None --" option. Based on thenewValue
of the category, it usesg_form.addOption()
multiple times to add the relevant subcategory choices (value and label). Note: ServiceNow has a built-in "Dependent Field" capability on dictionary entries which is the standard, no-code way to achieve this and is generally preferred over Client Scripts for this specific use case.
24. Display Idle Form Warning
Scenario: Warn the user if they have left a form open and idle (no interaction) for more than 15 minutes.
Solution: Use an onLoad
Client Script with JavaScript's setTimeout
and event listeners.
function onLoad() {
var idleTimeoutDuration = 15 * 60 * 1000; // 15 minutes in milliseconds
var idleTimer = null;
var warningDialog = null; // To hold reference to potential dialog
function showIdleWarning() {
// Hide previous warning if any
if (warningDialog) {
try { warningDialog.destroy(); } catch(e){}
warningDialog = null;
}
// Display a GlideModal warning (more user friendly than alert)
warningDialog = new GlideModal('glide_modal_confirm');
warningDialog.setTitle('Idle Session Warning');
warningDialog.setPreference('body', 'This form has been idle for over 15 minutes. Please save your work or continue editing.');
warningDialog.setPreference('focusTrap', true);
warningDialog.setPreference('onPromptCancel', resetTimer); // If they interact with dialog
warningDialog.setPreference('onPromptComplete', resetTimer); // If they interact with dialog
warningDialog.render();
// Optional: Add logic for auto-logout or redirection after a further period
}
function resetTimer() {
// Hide warning if visible
if (warningDialog) {
try { warningDialog.destroy(); } catch(e){}
warningDialog = null;
}
// Clear the existing timer
clearTimeout(idleTimer);
// Start a new timer
idleTimer = setTimeout(showIdleWarning, idleTimeoutDuration);
// jslog('Idle timer reset.'); // For debugging
}
// --- Event Listeners ---
// Reset timer on mouse movement, key press, or form interaction
document.addEventListener('mousemove', resetTimer, true);
document.addEventListener('keypress', resetTimer, true);
// Add listeners for form field changes etc. if needed, though keypress/mousemove often suffice
// --- Initialize ---
// Start the initial timer when the form loads
resetTimer();
}
-
Type:
onLoad
-
Explanation: This
onLoad
script sets up an idle timer usingsetTimeout
. It defines aresetTimer
function that clears any existing timer and starts a new one. Event listeners for mouse movement and key presses are added to the document; whenever these events occur,resetTimer
is called, effectively restarting the countdown. If the timer reaches its duration (idleTimeoutDuration
) without being reset, theshowIdleWarning
function is executed, displaying a warning message (usingGlideModal
for a better experience thanalert
). Interacting with the warning dialog also resets the timer.
25. Validate Numeric Input Within Range
Scenario: Ensure that a "Quantity" field (quantity
) only accepts integer values between 1 and 100.
Solution: Use an onChange
Client Script on the quantity
field.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
g_form.hideFieldMsg('quantity', true); // Clear msg if empty
return;
}
var fieldName = 'quantity';
var minVal = 1;
var maxVal = 100;
// Clear previous messages
g_form.hideFieldMsg(fieldName, true);
// Try to convert to integer
var intValue = parseInt(newValue, 10);
// Check if it's not a number, not an integer, or outside the range
if (isNaN(intValue) || intValue != newValue || intValue < minVal || intValue > maxVal) {
// Show error message
g_form.showFieldMsg(fieldName, 'Quantity must be a whole number between ' + minVal + ' and ' + maxVal + '.', 'error');
// Optional: Clear the invalid value
// g_form.setValue(fieldName, '');
}
}
-
Type:
onChange
-
Field:
quantity
-
Explanation: This script triggers when the
quantity
field changes. It attempts to parse thenewValue
into an integer usingparseInt()
. It then checks if the result isNaN
(Not-a-Number), if it differs from the original input (meaning it had decimals or non-numeric characters), or if it falls outside the allowed range (minVal
tomaxVal
). If any check fails, it displays an error message.
26. Hide Form Sections Based on User Roles
Scenario: Hide the "Admin Notes" form section (admin_notes_section
) for all users who do not have the itil_admin
role. (Note: UI Policies or Form Layout configuration by View are often preferred).
Solution: Use an onLoad
Client Script.
function onLoad() {
// Check if the user has the 'itil_admin' role
var isAdmin = g_user.hasRole('itil_admin');
// Get the name of the section to hide
// Section names might be tricky to get; inspect element or check Form Layout/Sections config.
// Assume the section name (control name) is 'admin_notes_section'
var sectionName = 'admin_notes_section'; // Replace with the actual internal name/id
// If the user is NOT an admin
if (!isAdmin) {
// Hide the section
// Note: g_form.setSectionDisplay() needs the section's *control name* or *sys_id*
// Finding the control name reliably can be tricky. Inspect the element to find its ID or name.
try {
// Try using the section name directly (might work in some versions/contexts)
var section = g_form.getSection(sectionName); // This API might not exist or work reliably
if(section){
g_form.setSectionDisplay(sectionName, false);
} else {
// Fallback: try common naming patterns if direct lookup fails
// Example: tab_caption_text.toLowerCase().replace(/ /g, '_') might be the pattern
// Or iterate through sections if possible:
var sections = g_form.getSections(); // This might return the section elements
for (var i = 0; i < sections.length; i++) {
// Check section label or other attributes to identify the correct one
// This part is highly dependent on ServiceNow version and DOM structure
// var sectionLabel = sections[i].textContent || sections[i].innerText;
// if (sectionLabel && sectionLabel.trim() === 'Admin Notes') {
// g_form.setSectionDisplay(sections[i], false); // Pass element itself
// break;
// }
}
// If still not found, hiding might need more specific DOM inspection or different approach
jslog('Could not reliably find section: ' + sectionName + ' to hide.');
}
} catch (e) {
jslog('Error hiding section ' + sectionName + ': ' + e);
}
}
}
-
Type:
onLoad
-
Explanation: This script runs when the form loads. It checks if the user has the
itil_admin
role. If not, it attempts to hide the specified form section usingg_form.setSectionDisplay()
. Finding the correct section name/ID programmatically can be difficult and unreliable across versions. Inspecting the form's HTML or using Form Layout configuration based on View (controlled by roles) is often a more robust approach than scripting section visibility.
27. Display Progress Indicator During Submission
Scenario: Show a "Submitting..." message or spinner while an onSubmit
script performs potentially long validation (like an AJAX call) before allowing or denying submission.
Solution: Use an onSubmit
Client Script, often combined with GlideAjax
for asynchronous checks.
function onSubmit() {
// --- Synchronous Example (less common for long tasks) ---
// If validation is quick and synchronous
// g_form.addInfoMessage('Validating...'); // Show indicator
// var isValid = performQuickValidation();
// g_form.clearMessages(); // Clear indicator
// return isValid;
// --- Asynchronous Example (using GlideAjax) ---
// Prevent default submission initially
var sys_id = g_form.getUniqueValue(); // Needed for GlideAjax if checking against current record
var fieldToCheck = g_form.getValue('some_field_to_validate');
// 1. Display the progress indicator
// Using a specific GlideModal or simple message
// NOTE: Showing UI *during* onSubmit is tricky as the UI may already be transitioning.
// A better UX pattern might be to disable the submit button and show a spinner *before* onSubmit is triggered (e.g., on button click event).
// However, if working strictly within onSubmit:
var progressMsgKey = 'submit_progress'; // Unique key for the message
g_form.addInfoMessage('Validating data, please wait...', progressMsgKey);
// Optionally disable submit buttons here if possible (difficult inside onSubmit)
// 2. Initiate the Asynchronous call (cannot use ga.getXMLWait in onSubmit)
var ga = new GlideAjax('MyValidationUtils'); // Your Script Include
ga.addParam('sysparm_name', 'validateDataAsync');
ga.addParam('sysparm_record_sysid', sys_id);
ga.addParam('sysparm_field_value', fieldToCheck);
// We CANNOT wait here. onSubmit must return true or false synchronously.
// This pattern inside onSubmit is fundamentally flawed for async validation
// because the form submission won't wait for the AJAX response.
// --- CORRECTED APPROACH: Validate *before* submit trigger ---
// You would typically intercept the *click* event of the Submit button,
// perform the AJAX validation *then*, and if valid, *then* trigger the actual form submission programmatically.
// This cannot be done purely with an onSubmit Client Script.
// --- Alternative: onSubmit for simple checks, then *maybe* async ---
// Perform all possible synchronous checks first
if (g_form.getValue('mandatory_field') == '') {
g_form.addErrorMessage('Mandatory field missing.');
g_form.hideFieldMsg(progressMsgKey, true); // Hide progress message
return false; // Fail fast
}
// If synchronous checks pass, maybe proceed, but async validation here is problematic.
// Server-side validation (Business Rule) is the reliable way for checks requiring server data.
// IF you *must* use async in onSubmit (discouraged), you might try to always
// return false, do AJAX, and if valid, call g_form.save() or similar command
// from the AJAX callback. This can lead to complex state management.
// --- Simplest "Indicator" in onSubmit (if validation is quick enough) ---
g_form.addInfoMessage('Submitting...', 'submit_indicator');
// Perform quick synchronous checks... if they fail:
// g_form.hideFieldMsg('submit_indicator', true); // Remove indicator
// return false;
// If checks pass:
return true; // Allow submit, indicator message will disappear on navigation
} // End onSubmit
/*
Conclusion: Reliably showing a progress indicator for *asynchronous* operations
initiated *within* an onSubmit script is problematic due to the synchronous nature
of the onSubmit event itself. The best practice is server-side validation (Business Rules)
or handling async validation *before* triggering the submit action (e.g., custom UI Action).
*/
-
Type:
onSubmit
-
Explanation: Showing a progress indicator during potentially long operations within
onSubmit
is difficult becauseonSubmit
must returntrue
orfalse
synchronously. Asynchronous operations likeGlideAjax
don't wait.- For very quick synchronous checks, you could briefly show/hide a message using
g_form.addInfoMessage/clearMessages
. - For asynchronous validation (the usual reason for needing a progress indicator),
onSubmit
is the wrong place. The validation should happen before the submit is attempted (e.g., via a custom UI Action that performs AJAX, shows a spinner, then callsg_form.submit()
if valid) or handled server-side with a Business Rule. The example code highlights these limitations.
- For very quick synchronous checks, you could briefly show/hide a message using
28. Validate URL Formats
Scenario: Ensure that input in a "Website" field (u_website
) is a valid-looking URL (starts with http:// or https://).
Solution: Use an onChange
Client Script on the u_website
field.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
g_form.hideFieldMsg('u_website', true); // Clear msg if empty
return;
}
var fieldName = 'u_website';
// Basic regex checking for http:// or https:// followed by some characters
// This is a simple check, not a full RFC-compliant URL validation
var urlPattern = /^https?:\/\/[^\s/$.?#].[^\s]*$/i;
// Trim whitespace before testing
var trimmedValue = newValue.trim();
if (trimmedValue !== '' && !urlPattern.test(trimmedValue)) {
// Show error message
g_form.showFieldMsg(fieldName, 'Please enter a valid URL starting with http:// or https://', 'error');
} else {
// Clear any previous error message
g_form.hideFieldMsg(fieldName, true);
}
}
-
Type:
onChange
-
Field:
u_website
-
Explanation: This script triggers when the website field changes. It uses a regular expression (
urlPattern
) to check if the input (after trimming whitespace) starts withhttp://
orhttps://
followed by typical URL characters. If the format is invalid, it displays an error message.
29. Automatically Calculate Due Dates
Scenario: When a "Priority" (priority
) is set on an Incident, automatically calculate and set the "Due Date" (due_date
) based on SLA definitions (e.g., P1 = 4 hours, P2 = 8 hours). Note: SLAs are typically handled by the ServiceNow SLA engine, but this shows a client-side calculation concept.
Solution: Use an onChange
Client Script on priority
. This requires date/time calculations, best done using GlideAjax
to leverage server-side GlideDateTime
and DurationCalculator
.
-
Script Include (
AjaxDateTimeUtils
):var AjaxDateTimeUtils = Class.create(); AjaxDateTimeUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, { calculateDueDate: function() { var priority = this.getParameter('sysparm_priority'); var startDateStr = this.getParameter('sysparm_start_date'); // e.g., Opened date var businessHours = parseInt(this.getParameter('sysparm_business_hours'), 10); // Duration in hours if (!priority || !businessHours || isNaN(businessHours)) { return ''; // Not enough info } // Use DurationCalculator for business hour calculations if needed // This example assumes simple hour addition for simplicity // Real world needs DurationCalculator with schedule // Use GlideDateTime for reliable date math var gdt; if (startDateStr) { gdt = new GlideDateTime(startDateStr); // Start from a specific date/time } else { gdt = new GlideDateTime(); // Start from now } // Add the specified number of hours gdt.addSeconds(businessHours * 3600); // Add hours converted to seconds // Return the calculated date/time in display format for the user // Or return in internal format if g_form.setValue handles it return gdt.getDisplayValue(); // Or gdt.getValue() for internal format }, type: 'AjaxDateTimeUtils' });
-
Client Script (
onChange
onpriority
):function onChange(control, oldValue, newValue, isLoading, isTemplate) { if (isLoading || newValue === '') { // Optional: Clear due date if priority is cleared? // g_form.setValue('due_date', ''); return; } var priority = newValue; var businessHoursToAdd = 0; // Define duration based on priority (adjust values as needed) if (priority == '1') { // P1 businessHoursToAdd = 4; } else if (priority == '2') { // P2 businessHoursToAdd = 8; } else if (priority == '3') { // P3 businessHoursToAdd = 24; } else { // No specific due date for other priorities or clear it g_form.setValue('due_date', ''); return; } // Get a start date (e.g., opened_at) if calculation should start from then var startDate = g_form.getValue('opened_at'); // Or use current time if empty // Call GlideAjax var ga = new GlideAjax('AjaxDateTimeUtils'); ga.addParam('sysparm_name', 'calculateDueDate'); ga.addParam('sysparm_priority', priority); ga.addParam('sysparm_business_hours', businessHoursToAdd); ga.addParam('sysparm_start_date', startDate); // Pass start date if needed ga.getXML(handleDueDateResponse); } function handleDueDateResponse(response) { var answer = response.responseXML.documentElement.getAttribute("answer"); if (answer) { // Set the due date field. 'answer' is expected to be in user's display format g_form.setValue('due_date', answer); g_form.addInfoMessage('Due date calculated based on priority.'); } else { // Handle cases where calculation failed or returned empty // g_form.setValue('due_date', ''); } }
-
Type:
onChange
(Client Script), GlideAjax, Script Include -
Field:
priority
,due_date
-
Explanation: When
priority
changes, the Client Script determines the corresponding business hours duration. It then calls aGlideAjax
function, passing the priority and duration. The server-side Script Include usesGlideDateTime
to add the duration (potentially usingDurationCalculator
for schedule awareness, though simplified here) to the current time or a specified start date. The calculated due date/time string is returned to the client, which then populates thedue_date
field. Note: The actual ServiceNow SLA Engine is the standard, much more powerful way to manage due dates based on complex conditions and schedules.
30. Prevent Duplicate Records (Client-Side Check)
Scenario: Before submitting a new Hardware Asset record, check if another asset with the same "Serial Number" (serial_number
) already exists. Warn the user and prevent submission if a duplicate is found.
Solution: Use an onSubmit
Client Script with GlideAjax
. Warning: This client-side check is subject to race conditions (two users submitting simultaneously). A server-side onBefore
Business Rule with setAbortAction(true)
is the most reliable way to prevent duplicates.
-
Script Include (
AjaxValidationUtils
):var AjaxValidationUtils = Class.create(); AjaxValidationUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, { checkDuplicateSerial: function() { var serial = this.getParameter('sysparm_serial'); var currentSysId = this.getParameter('sysparm_current_sysid'); // To exclude current record if editing if (!serial) { return 'false'; // No serial to check } var assetGr = new GlideRecord('alm_hardware'); // Or your asset table assetGr.addQuery('serial_number', serial); // If checking on an existing record, exclude itself from the check if (currentSysId && currentSysId != '-1') { assetGr.addQuery('sys_id', '!=', currentSysId); } assetGr.query(); if (assetGr.next()) { // Found a duplicate return 'true'; } else { // No duplicate found return 'false'; } }, type: 'AjaxValidationUtils' });
-
Client Script (
onSubmit
):function onSubmit() { // Only run this check for new records or if serial number changed significantly if (!g_form.isNewRecord() && !g_form.isFieldModified('serial_number')) { return true; // Don't re-check if serial hasn't changed on existing record } var serialNumber = g_form.getValue('serial_number'); if (serialNumber == '') { return true; // Allow submit if serial is empty (unless it's mandatory) } // --- Asynchronous check approach (problematic in onSubmit) --- // Standard GlideAjax is async and onSubmit expects immediate true/false. // You cannot reliably use async GlideAjax here to *prevent* submission. // --- Synchronous check approach (using getXMLWait - BLOCKS UI - BAD PRACTICE) --- /* var ga = new GlideAjax('AjaxValidationUtils'); ga.addParam('sysparm_name', 'checkDuplicateSerial'); ga.addParam('sysparm_serial', serialNumber); ga.addParam('sysparm_current_sysid', g_form.getUniqueValue()); // Pass current record sys_id ga.getXMLWait(); // !!! WARNING: Synchronous call - Blocks browser !!! var isDuplicate = ga.getAnswer(); if (isDuplicate == 'true') { g_form.addErrorMessage('Duplicate Prevention: An asset with serial number "' + serialNumber + '" already exists.'); return false; // Prevent submission } else { return true; // Allow submission } */ // --- Conclusion --- // Client-side duplicate prevention within onSubmit using GlideAjax is either // unreliable (async) or bad practice (sync blocking call). // The recommended solution is an onBefore Insert/Update Business Rule. // If you *must* do it client-side, consider validating onChange of the serial number field instead. // Or use the problematic synchronous call shown above (discouraged). alert('Error: Client-side duplicate check in onSubmit is not recommended. Please implement a server-side Business Rule.'); return false; // Fail safely by default if using this placeholder structure } // End onSubmit
-
Type:
onSubmit
(Client Script), GlideAjax, Script Include -
Field:
serial_number
-
Explanation: The
onSubmit
script gets theserial_number
. It should callGlideAjax
to check server-side if another record exists with the same serial number (excluding the current record if it's an update).-
Problem: Standard
GlideAjax
(getXML
) is asynchronous, butonSubmit
needs a synchronoustrue
orfalse
return value immediately. The AJAX response arrives too late to stop the original submission event. - Using synchronous
GlideAjax
(getXMLWait
) blocks the user's browser and provides a very poor user experience, so it is strongly discouraged. -
Best Practice: Implement duplicate checks using a server-side
onBefore
Business Rule which can query efficiently and reliably abort the database transaction if a duplicate is found. The client script example above includes the synchronous code (commented out with warnings) primarily for illustration of the wrong way, and emphasizes using a Business Rule instead.
-
Problem: Standard
31. Real-time Credit Card Number Validation (Conceptual)
Scenario: Validate the format and potentially the Luhn checksum of a credit card number entered into a field (u_credit_card_number
) in real-time as the user types. Warning: Handling raw credit card numbers directly in form fields is highly discouraged due to PCI DSS compliance requirements. Use secure, integrated payment gateways.
Solution: This is purely conceptual for the validation logic. Do not store/handle raw credit card numbers this way in production. Use an onChange
Client Script.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) return;
var fieldName = 'u_credit_card_number';
var cardNumber = newValue.replace(/[-\s]/g, ''); // Remove spaces and hyphens
g_form.hideFieldMsg(fieldName, true); // Clear previous messages
if (cardNumber === '') return; // Ignore empty value
// Basic Format Check (e.g., 13-19 digits)
if (!/^\d{13,19}$/.test(cardNumber)) {
g_form.showFieldMsg(fieldName, 'Invalid format. Enter 13-19 digits.', 'error');
return;
}
// Luhn Algorithm Check (Checksum)
if (!luhnCheck(cardNumber)) {
g_form.showFieldMsg(fieldName, 'Invalid credit card number (checksum failed).', 'error');
return;
}
// If passes basic checks, maybe indicate type (optional, requires more logic)
// var cardType = detectCardType(cardNumber);
// if (cardType) {
// g_form.showFieldMsg(fieldName, 'Detected Card Type: ' + cardType, 'info');
// } else {
// g_form.showFieldMsg(fieldName, 'Card number looks valid, but type unknown.', 'info');
// }
}
// Luhn Algorithm Check function
function luhnCheck(val) {
var sum = 0;
var numDigits = val.length;
var parity = numDigits % 2;
for (var i = 0; i < numDigits; i++) {
var digit = parseInt(val.charAt(i));
if (i % 2 == parity) digit *= 2;
if (digit > 9) digit -= 9;
sum += digit;
}
return (sum % 10) == 0;
}
// Optional: Function to detect card type based on prefix (simplified)
/*
function detectCardType(number) {
if (/^4/.test(number)) return 'Visa';
if (/^5[1-5]/.test(number)) return 'MasterCard';
if (/^3[47]/.test(number)) return 'American Express';
if (/^6(?:011|5)/.test(number)) return 'Discover';
// Add more types as needed
return null;
}
*/
// --- SECURITY WARNING ---
// Storing or processing raw credit card numbers directly in ServiceNow fields
// poses significant security risks and PCI compliance challenges.
// Always use a dedicated, compliant payment gateway integration instead.
// This example is purely for demonstrating client-side validation logic.
-
Type:
onChange
-
Field:
u_credit_card_number
-
Explanation: This
onChange
script triggers as the user types in the (highly discouraged) credit card field. It removes spaces/hyphens, performs a basic length check, and then applies the Luhn algorithm (luhnCheck
function) to validate the checksum. Error messages are shown if checks fail. Crucially, this example ignores the severe security implications. Never implement this in production without a secure payment gateway.
32. Auto-populate User Info for Logged-in User
Scenario: When a new "Request" form is loaded, automatically populate the "Requested For" (requested_for
) and "Department" (department
) fields with the details of the currently logged-in user.
Solution: Use an onLoad
Client Script.
function onLoad() {
// Run only for new records to avoid overwriting existing data
if (!g_form.isNewRecord()) {
return;
}
// Get the sys_id of the logged-in user
var currentUserSysId = g_user.userID;
// Set the 'Requested For' field (assuming it's a reference to sys_user)
g_form.setValue('requested_for', currentUserSysId);
// Get the user's department - needs department sys_id
// Option 1: If g_user object has department cached (less reliable)
// var userDepartmentSysId = g_user.department;
// if (userDepartmentSysId) {
// g_form.setValue('department', userDepartmentSysId);
// } else {
// // Need to fetch it if not readily available
// console.log('Department sys_id not directly available on g_user. Consider GlideAjax.');
// }
// Option 2: Use getReference (simpler if 'requested_for' is set first)
// Ensure 'requested_for' is set before calling getReference on it
g_form.getReference('requested_for', function(userRecord) {
if (userRecord && userRecord.department) {
g_form.setValue('department', userRecord.department);
} else {
// Fallback or error handling if department can't be found
jslog('Could not retrieve department for the current user.');
// Could use GlideAjax here as a fallback if needed
}
});
// Option 3: Use GlideAjax (most reliable if department isn't cached or 'requested_for' isn't set yet)
/*
var ga = new GlideAjax('AjaxClientUtils'); // Assuming utility script include
ga.addParam('sysparm_name','getUserDepartment');
ga.addParam('sysparm_user_id', currentUserSysId);
ga.getXML(function(response) {
var answer = response.responseXML.documentElement.getAttribute("answer");
if (answer) {
g_form.setValue('department', answer); // Assuming answer is dept sys_id
}
});
// Remember to add 'getUserDepartment' to your Script Include (see Q20)
*/
}
-
Type:
onLoad
-
Explanation: This script runs when a new record form loads. It gets the logged-in user's sys_id from
g_user.userID
and sets therequested_for
field. To set the department, it usesg_form.getReference
on the just-populatedrequested_for
field. The callback function receives the user record details and sets thedepartment
field using thedepartment
sys_id from the user record. UsinggetReference
is often simpler thanGlideAjax
if you already have the user's sys_id on the form.
33. Dynamically Update Dependent Choice Field Choices
Scenario: This is essentially the same as Scenario 23: updating "Subcategory" options based on "Category". ServiceNow's built-in "Dependent Field" feature is the standard solution.
Solution: (See Solution 23 for the Client Script method). Use an onChange
Client Script on the primary choice field (category
).
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) {
return;
}
var dependentField = 'subcategory'; // The field whose choices depend on category
// Clear existing options and the current value in the dependent field
g_form.clearOptions(dependentField);
g_form.setValue(dependentField, ''); // Clear selected value
// Add a default '-- None --' option
g_form.addOption(dependentField, '', '-- None --');
// Use GlideAjax to fetch relevant choices from the server based on 'newValue' (Category)
// This is more dynamic than hardcoding choices in the client script
var ga = new GlideAjax('ChoiceListUtils'); // Example Script Include name
ga.addParam('sysparm_name', 'getSubcategoryChoices');
ga.addParam('sysparm_category', newValue); // Pass the selected category value
ga.addParam('sysparm_dependent_field_name', dependentField); // Pass field name if needed by SI
ga.getXML(populateSubcategoryOptions);
}
// Callback function to populate the dependent choice list
function populateSubcategoryOptions(response) {
var answer = response.responseXML.documentElement.getAttribute("answer");
if (answer) {
try {
var choices = JSON.parse(answer); // Expecting JSON [{value: 'val', label: 'lbl'}, ...]
if (choices && choices.length > 0) {
choices.forEach(function(choice) {
g_form.addOption('subcategory', choice.value, choice.label);
});
}
} catch (e) {
jslog('Error parsing subcategory choices: ' + e);
}
}
}
// --- Add Script Include 'ChoiceListUtils' ---
/*
var ChoiceListUtils = Class.create();
ChoiceListUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
getSubcategoryChoices: function() {
var categoryValue = this.getParameter('sysparm_category');
var dependentFieldName = this.getParameter('sysparm_dependent_field_name');
var choices = [];
// Query sys_choice table or a custom table based on the categoryValue
// Example: Querying sys_choice for a specific table and dependent value
var choiceGr = new GlideRecord('sys_choice');
choiceGr.addQuery('name', 'incident'); // Target table name (e.g., incident)
choiceGr.addQuery('element', dependentFieldName); // e.g., 'subcategory'
choiceGr.addQuery('dependent_value', categoryValue); // Filter by category
choiceGr.addQuery('inactive', false);
choiceGr.orderBy('sequence');
choiceGr.query();
while (choiceGr.next()) {
choices.push({
value: choiceGr.getValue('value'),
label: choiceGr.getValue('label')
});
}
return JSON.stringify(choices);
},
type: 'ChoiceListUtils'
});
*/
-
Type:
onChange
(Client Script), GlideAjax, Script Include -
Fields:
category
,subcategory
-
Explanation: This approach is more dynamic than hardcoding choices (as in Solution 23). When the
category
changes, it clears thesubcategory
options. It then usesGlideAjax
to call a Script Include (getSubcategoryChoices
). The Script Include queries thesys_choice
table (or another source) for choices associated with the selectedcategory
(using thedependent_value
field onsys_choice
). It returns the relevant choices as a JSON array. The client-side callback function parses the JSON and usesg_form.addOption()
to populate thesubcategory
field. Again, the built-in "Dependent Field" feature is usually preferred.
34. Display Tooltips for Form Fields
Scenario: Add a helpful tooltip that appears when a user hovers over the "Configuration Item" (cmdb_ci
) field label, explaining what it's used for.
Solution: Use an onLoad
Client Script to modify the label element's title
attribute. (DOM manipulation - use cautiously).
function onLoad() {
var fieldName = 'cmdb_ci';
var tooltipText = 'Select the specific hardware or software component related to this record.';
try {
// Find the label element associated with the field
// This relies on the DOM structure (e.g., label's 'for' attribute matching input's id)
var control = g_form.getControl(fieldName); // Get the input/select element
if (control && control.id) {
// Standard label 'for' attribute usually matches control 'id'
var labelElement = $$('label[for="' + control.id + '"]')[0]; // Using Prototype.js $$ selector
if (labelElement) {
// Set the title attribute, which browsers typically display as a tooltip
labelElement.setAttribute('title', tooltipText);
} else {
jslog('Could not find label element for field: ' + fieldName);
}
} else {
jslog('Could not find control element for field: ' + fieldName);
}
} catch (e) {
jslog('Error setting tooltip for ' + fieldName + ': ' + e);
}
}
-
Type:
onLoad
-
Explanation: This script runs when the form loads. It attempts to find the HTML
label
element associated with thecmdb_ci
field. It does this by first getting the field's control (input/select element) usingg_form.getControl()
, then finding the label whosefor
attribute matches the control'sid
(using the$$
selector, common in ServiceNow's client environment). If found, it sets the label'stitle
attribute to the desiredtooltipText
. Browsers render thetitle
attribute as a native tooltip on hover. Caution: Relies on specific DOM structure and selectors ($$
), which might change across UI versions. Configuring help text via the dictionary entry for the field is a more standard approach.
35. Restrict Past Dates in Date Field
Scenario: Prevent users from selecting a date in the past for the "Start Date" (start_date
) field.
Solution: Use an onChange
Client Script on the start_date
field.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
g_form.hideFieldMsg('start_date', true); // Clear msg if empty
return;
}
var fieldName = 'start_date';
// Clear previous messages
g_form.hideFieldMsg(fieldName, true);
try {
// Parse selected date (needs robust parsing - see Q5/Q19)
var selectedDate = new Date(getDateFromFormat(newValue, g_user_date_format)); // Using date format
// Get current date, normalized to the start of the day (midnight)
var today = new Date();
today.setHours(0, 0, 0, 0);
// Normalize selected date if time isn't relevant
// selectedDate.setHours(0, 0, 0, 0); // Uncomment if time part should be ignored
if (selectedDate < today) {
// Date is in the past
g_form.setValue(fieldName, ''); // Clear the invalid date
g_form.showFieldMsg(fieldName, 'Start Date cannot be in the past.', 'error');
}
} catch(e) {
jslog('Error validating past date for ' + fieldName + ': ' + e);
// Optionally show a generic error if parsing fails
// g_form.showFieldMsg(fieldName, 'Invalid date format.', 'error');
}
}
// Assume getDateFromFormat exists (see Q5 explanation)
// Note: Newer ServiceNow versions offer client-side GlideDate/GlideDateTime APIs
// which are much better for date comparisons than manual parsing. Example:
/*
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue == '') return;
var fieldName = 'start_date';
g_form.hideFieldMsg(fieldName, true);
var selectedDate = new GlideDate();
selectedDate.setDisplayValue(newValue); // Use display value from form
var today = new GlideDate(); // Gets today's date
if (selectedDate.before(today)) {
g_form.setValue(fieldName, '');
g_form.showFieldMsg(fieldName, 'Start Date cannot be in the past.', 'error');
}
}
*/
-
Type:
onChange
-
Field:
start_date
-
Explanation: When the
start_date
field changes, the script parses the selected date. It gets the current date and sets its time to midnight to represent the start of today. It then compares the selected date with today's date. If the selected date is earlier than today, it clears the field and displays an error message. The commented section shows how much simpler this becomes using the modernGlideDate
client-side API if available.
36. Limit Character Count in Text Field
Scenario: Limit the input in a single-line "Title" field (short_description
) to a maximum of 80 characters.
Solution: Use an onChange
Client Script.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) {
return;
}
var fieldName = 'short_description'; // Or your specific text field name
var maxLength = 80;
// Clear previous messages
g_form.hideFieldMsg(fieldName, true);
if (newValue.length > maxLength) {
// Display an info/warning message
g_form.showFieldMsg(fieldName, 'Input has exceeded the maximum length of ' + maxLength + ' characters. It will be truncated upon saving if not corrected.', 'info');
// Option 1: Warn only (as above) - let server handle truncation if field max_length is set.
// Option 2: Actively truncate in the client (can be jarring for user)
// var truncatedValue = newValue.substring(0, maxLength);
// g_form.setValue(fieldName, truncatedValue);
// g_form.showFieldMsg(fieldName, 'Input truncated to ' + maxLength + ' characters.', 'info');
}
}
// Also consider setting the 'max_length' attribute on the field's dictionary entry.
// This enforces the limit at the database level and often provides basic browser enforcement too.
-
Type:
onChange
-
Field:
short_description
(or other text field) -
Explanation: This script triggers when the field changes. It checks if the
newValue
length exceedsmaxLength
. If it does, it displays an informational message warning the user. Alternatively (commented out), it could actively truncate the input usingsubstring()
andg_form.setValue()
. It's generally better UX to just warn the user. Setting themax_length
attribute in the dictionary is the primary enforcement mechanism.
37. Auto-populate from External Source (via MID Server/REST)
Scenario: When a user enters an IP address in a field (u_ip_address
), use an external API (via a MID server or direct REST call from server-side) to get geolocation data (e.g., City, Country) and populate corresponding fields (u_ip_city
, u_ip_country
).
Solution: Requires Client Script, GlideAjax, Script Include, and potentially a REST Message configuration in ServiceNow.
-
Configure REST Message (ServiceNow Platform): Set up a REST Message (e.g., "IP Geolocation API") pointing to the external API endpoint (e.g.,
ip-api.com
,ipinfo.io
). Define GET method, authentication if needed, and parameters (like the IP address). -
Script Include (
AjaxExternalDataUtils
):var AjaxExternalDataUtils = Class.create(); AjaxExternalDataUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, { getGeoLocationForIP: function() { var ipAddress = this.getParameter('sysparm_ip'); var result = { city: '', country: '' }; // Default empty result if (!ipAddress) { return JSON.stringify(result); } try { // Name of the configured REST Message var restMessageName = 'IP Geolocation API'; // Or whatever you named it var restMethodName = 'GET'; // Or the specific method name var r = new sn_ws.RESTMessageV2(restMessageName, restMethodName); // Set variables defined in the REST message (e.g., an 'ipaddress' variable) r.setStringParameter('ipaddress', ipAddress); // Execute the REST call (potentially via MID if endpoint isn't public) var response = r.execute(); // Use executeAsync for non-blocking server-side var httpStatus = response.getStatusCode(); var responseBody = response.getBody(); if (httpStatus == 200) { var responseObj = JSON.parse(responseBody); // Parse the response - structure depends on the specific API used // Example for ip-api.com: if (responseObj.status == 'success') { result.city = responseObj.city || ''; result.country = responseObj.country || ''; } else { gs.warn('IP Geolocation API failed for ' + ipAddress + ': ' + responseObj.message); } } else { gs.error('IP Geolocation HTTP Error: ' + httpStatus + ' for IP: ' + ipAddress + ' Body: ' + responseBody); } } catch (ex) { gs.error('IP Geolocation Script Error for IP ' + ipAddress + ': ' + ex.getMessage()); } return JSON.stringify(result); }, type: 'AjaxExternalDataUtils' });
-
Client Script (
onChange
onu_ip_address
):function onChange(control, oldValue, newValue, isLoading, isTemplate) { if (isLoading || newValue === '') { // Clear geo fields if IP is cleared if (newValue === '') { g_form.setValue('u_ip_city', ''); g_form.setValue('u_ip_country', ''); } return; } // Basic IP format check (optional but recommended) // var ipPattern = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/; // if (!ipPattern.test(newValue)) return; // Don't query if format is wrong // Call GlideAjax var ga = new GlideAjax('AjaxExternalDataUtils'); ga.addParam('sysparm_name', 'getGeoLocationForIP'); ga.addParam('sysparm_ip', newValue); ga.getXML(handleGeoResponse); } function handleGeoResponse(response) { var answer = response.responseXML.documentElement.getAttribute("answer"); g_form.setValue('u_ip_city', ''); // Clear fields first g_form.setValue('u_ip_country', ''); if (answer) { try { var geoData = JSON.parse(answer); g_form.setValue('u_ip_city', geoData.city); g_form.setValue('u_ip_country', geoData.country); if (geoData.city || geoData.country) { g_form.addInfoMessage('Geolocation data populated for IP address.'); } else { g_form.showFieldMsg('u_ip_address', 'Could not retrieve geolocation data for this IP.', 'info'); } } catch (e) { jslog('Error parsing geolocation response: ' + e); } } }
-
Type:
onChange
(Client Script), GlideAjax, Script Include, REST Message -
Field:
u_ip_address
,u_ip_city
,u_ip_country
-
Explanation: When the
u_ip_address
field changes, the Client Script triggers aGlideAjax
call. The Script Include receives the IP address. It then usesRESTMessageV2
to call the pre-configured external REST API. The API call might go through a MID server if the endpoint requires it. The Script Include parses the JSON response from the external API, extracts the city and country, and returns them as a JSON string. The Client Script callback function receives this data, parses it, and populates theu_ip_city
andu_ip_country
fields.
38. Validate Input Against Predefined List
Scenario: Ensure that a text field "Cost Center" (u_cost_center
) only accepts values from a specific list (e.g., "CC100", "CC200", "CC500").
Solution: Use an onChange
Client Script.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
g_form.hideFieldMsg('u_cost_center', true); // Clear msg if empty
return;
}
var fieldName = 'u_cost_center';
var allowedValues = ['CC100', 'CC200', 'CC500']; // The predefined list
// Make comparison case-insensitive (optional)
var upperNewValue = newValue.toUpperCase();
var upperAllowedValues = allowedValues.map(function(v) { return v.toUpperCase(); });
// Clear previous message
g_form.hideFieldMsg(fieldName, true);
// Check if the entered value (case-insensitive) is in the allowed list
if (upperAllowedValues.indexOf(upperNewValue) === -1) {
// Value not found in the list
g_form.showFieldMsg(fieldName, 'Invalid Cost Center. Please enter one of the allowed values: ' + allowedValues.join(', '), 'error');
// Optionally clear the field
// g_form.setValue(fieldName, '');
}
}
// Note: For longer or dynamic lists, consider using GlideAjax to validate against
// a server-side table or property instead of hardcoding in the client script.
// A reference field or choice list is usually a better UX than free-text validation.
-
Type:
onChange
-
Field:
u_cost_center
-
Explanation: This script triggers when the cost center field changes. It defines an array
allowedValues
containing the valid entries. It compares the enterednewValue
(converted to uppercase for case-insensitivity) against the allowed list (also converted). If the entered value is not found in the array (indexOf
returns -1), it displays an error message listing the valid options. For better user experience, consider using a Choice List or Reference field instead of free text.
39. Dynamically Adjust Field Visibility Based on Screen Size (Conceptual)
Scenario: Hide a less important field, like "User Agent String" (u_user_agent
), when the form is viewed on a small screen (e.g., mobile).
Solution: This is typically handled by responsive design frameworks (like Bootstrap used in Service Portal) or CSS media queries, not standard Platform UI Client Scripts. Client Scripts could try to detect screen size, but it's not their primary purpose and less robust than CSS.
CSS Approach (Preferred): Define CSS rules (e.g., in a Theme or CSS include for Portal, or Global UI Script/Style Sheet for Platform UI) using media queries.
/* Example CSS */
@media (max-width: 768px) { /* Target devices narrower than 768px */
/* Selector needs to precisely target the form group/container for the field */
/* Inspect element to find a stable selector for the field's row/container */
/* Example: might be based on label 'for' or input 'id' */
/* This selector is JUST AN EXAMPLE and likely needs adjustment */
label[for="sys_display.incident.u_user_agent"], /* Label */
#sys_display\.incident\.u_user_agent, /* Reference lookup icon */
#incident\.u_user_agent /* Input field */
/* Add other elements related to the field if needed */
{
display: none !important; /* Hide the elements */
}
/* OR target a common parent container if one exists */
.form-group /* Common class in some frameworks */ {
/* More specific selector needed here */
}
}
Client Script Approach (Less Ideal):
Use an onLoad
Client Script and potentially a listener for window resize.
function onLoad() {
function checkScreenSize() {
var fieldName = 'u_user_agent';
var smallScreenWidth = 768; // Example breakpoint
if (window.innerWidth < smallScreenWidth) {
g_form.setVisible(fieldName, false);
// Might need to hide label separately using DOM manipulation (fragile)
} else {
g_form.setVisible(fieldName, true);
// Show label if hidden previously
}
}
// Check on load
checkScreenSize();
// Check on resize (can fire frequently, consider debouncing)
var resizeTimeout;
window.addEventListener('resize', function() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(checkScreenSize, 250); // Debounce resize events
});
}
-
Type: CSS Media Query (preferred) or
onLoad
Client Script (less ideal) -
Explanation: The preferred method is using CSS Media Queries to define styles that apply only below a certain screen width (
max-width
). These styles would setdisplay: none
on the elements associated with the field (label, input, container). The Client Script approach useswindow.innerWidth
to detect screen size on load and during resize events (with debouncing). It usesg_form.setVisible()
to hide/show the field control, but hiding the label cleanly often requires fragile DOM manipulation. CSS is generally much cleaner and more efficient for responsive visibility.
40. Prevent Form Submissions Outside Business Hours
Scenario: Prevent users from submitting a "Critical Change Request" form outside of defined business hours (e.g., Monday-Friday, 9 AM - 5 PM).
Solution: Use an onSubmit
Client Script with GlideAjax
to check the time against a schedule on the server.
-
Define a Schedule (ServiceNow Platform): Create or identify a Schedule record (e.g., "Standard Business Hours") under
System Scheduler > Schedules > Schedules
that defines your business hours and holidays. Note its sys_id. -
Script Include (
AjaxScheduleUtils
):var AjaxScheduleUtils = Class.create(); AjaxScheduleUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, { isWithinSchedule: function() { var scheduleSysId = this.getParameter('sysparm_schedule_sysid'); // Optional: Pass a specific datetime string to check // var dateTimeToCheck = this.getParameter('sysparm_datetime'); var gdt; // if (dateTimeToCheck) { // gdt = new GlideDateTime(dateTimeToCheck); // } else { gdt = new GlideDateTime(); // Check current server time // } if (!scheduleSysId) { gs.error('isWithinSchedule called without scheduleSysId'); return 'false'; // Fail safely } var schedule = new GlideSchedule(scheduleSysId); // Check if the current datetime is within the schedule if (schedule.isInSchedule(gdt)) { return 'true'; } else { return 'false'; } }, type: 'AjaxScheduleUtils' });
-
Client Script (
onSubmit
):function onSubmit() { // Only apply this check to specific types of records if needed // var changeType = g_form.getValue('type'); // if (changeType !== 'emergency') return true; // Example: only check non-emergency // --- Synchronous AJAX (getXMLWait - BAD PRACTICE - BLOCKS UI) --- var scheduleSysId = 'YOUR_SCHEDULE_SYS_ID_HERE'; // ** Replace with actual Schedule sys_id ** if (!scheduleSysId || scheduleSysId === 'YOUR_SCHEDULE_SYS_ID_HERE') { alert('Developer Error: Schedule sys_id not configured in onSubmit Client Script.'); return false; } var ga = new GlideAjax('AjaxScheduleUtils'); ga.addParam('sysparm_name', 'isWithinSchedule'); ga.addParam('sysparm_schedule_sysid', scheduleSysId); // ga.addParam('sysparm_datetime', g_form.getValue('some_date_field')); // Optional: check specific date // !!! WARNING: Using synchronous call to block submission. Discouraged. !!! ga.getXMLWait(); var isInSchedule = ga.getAnswer(); if (isInSchedule == 'false') { g_form.addErrorMessage('Critical Change Requests can only be submitted during standard business hours (Mon-Fri, 9 AM - 5 PM, excluding holidays).'); return false; // Prevent submission } else if (isInSchedule == 'true') { return true; // Allow submission } else { // Error occurred during check g_form.addErrorMessage('Could not verify business hours. Submission prevented.'); return false; } // --- Asynchronous approach (Requires different UX - see Q27/Q30 discussion) --- // Cannot reliably block submit with standard async GlideAjax here. // Would need pre-submit validation triggered differently. }
-
Type:
onSubmit
(Client Script), GlideAjax, Script Include, Schedule -
Explanation: The
onSubmit
script triggers before submission. It uses a synchronousGlideAjax
call (getXMLWait
) - which is bad practice as it freezes the user's browser - to call theAjaxScheduleUtils
Script Include. The Script Include takes the sys_id of a pre-defined Schedule record, gets the current server time usingGlideDateTime
, and usesGlideSchedule.isInSchedule()
to determine if the current time falls within the allowed schedule timeframe (considering weekdays, times, and holidays defined in the schedule). It returns 'true' or 'false'. The client script receives the answer; if 'false', it shows an error and prevents submission. Due to the reliance ongetXMLWait
, consider if this check is better performed server-side (Business Rule) or if the UX impact of a potentially frozen browser is acceptable.
41. Validate Special Characters
Scenario: Prevent users from entering specific special characters (e.g., !
, #
, $
, %
) in the user_name
field.
Solution: Use an onChange
Client Script.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
g_form.hideFieldMsg('user_name', true); // Clear msg if empty
return;
}
var fieldName = 'user_name';
// Regular expression to find disallowed characters. Square brackets define a character set.
// Characters inside need escaping if they are special regex characters (like $).
var disallowedCharsPattern = /[!#\$%]/; // Looking for !, #, $, or %
// More complex example: Allow only letters, numbers, underscore, dot
// var allowedPattern = /^[a-zA-Z0-9_\.]+$/;
// if (!allowedPattern.test(newValue)) { ... }
// Clear previous message
g_form.hideFieldMsg(fieldName, true);
// Check if the new value contains any of the disallowed characters
if (disallowedCharsPattern.test(newValue)) {
// Found a disallowed character
g_form.showFieldMsg(fieldName, 'The characters !, #, $, % are not allowed in the User Name.', 'error');
// Optionally remove the characters or clear the field
// g_form.setValue(fieldName, newValue.replace(/[!#\$%]/g, '')); // Removes disallowed chars
}
}
-
Type:
onChange
-
Field:
user_name
-
Explanation: This script triggers when the
user_name
field changes. It defines a regular expressiondisallowedCharsPattern
that matches if any of the specified characters (!
,#
,$
,%
) are present in thenewValue
. If thetest()
method returns true (a disallowed character is found), it displays an error message. Alternatively, you could define a pattern for allowed characters and test if the input doesn't match that.
42. Enforce Unique Values in Form Field (Client-Side Check)
Scenario: Similar to Scenario 30, but focusing on the uniqueness check itself. Ensure the "Employee ID" (u_employee_id
) entered is unique across all user records before submitting.
Solution: Use an onSubmit
Client Script with GlideAjax
. Again, server-side Business Rule is the reliable method.
-
Script Include (
AjaxValidationUtils
- reuse or create):var AjaxValidationUtils = Class.create(); AjaxValidationUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, { checkUniqueValue: function() { var tableName = this.getParameter('sysparm_table_name'); var fieldName = this.getParameter('sysparm_field_name'); var valueToCheck = this.getParameter('sysparm_value'); var currentSysId = this.getParameter('sysparm_current_sysid'); // Exclude current record if (!tableName || !fieldName || !valueToCheck) { gs.warn('checkUniqueValue called with missing parameters.'); return 'error'; // Indicate an error occurred } var gr = new GlideRecord(tableName); gr.addQuery(fieldName, valueToCheck); // If checking on an existing record, exclude itself if (currentSysId && currentSysId != '-1') { gr.addQuery('sys_id', '!=', currentSysId); } gr.setLimit(1); // We only need to know if at least one exists gr.query(); if (gr.next()) { return 'true'; // Value is NOT unique (duplicate found) } else { return 'false'; // Value IS unique (no duplicate found) } }, type: 'AjaxValidationUtils' });
-
Client Script (
onSubmit
):function onSubmit() { var fieldName = 'u_employee_id'; var tableName = 'sys_user'; // Table to check against // Only run check if field has changed or it's a new record if (!g_form.isNewRecord() && !g_form.isFieldModified(fieldName)) { return true; } var employeeId = g_form.getValue(fieldName); if (employeeId == '') { return true; // Allow submit if empty (unless mandatory) } // --- Synchronous AJAX (getXMLWait - BAD PRACTICE - BLOCKS UI) --- var ga = new GlideAjax('AjaxValidationUtils'); ga.addParam('sysparm_name', 'checkUniqueValue'); ga.addParam('sysparm_table_name', tableName); ga.addParam('sysparm_field_name', fieldName); ga.addParam('sysparm_value', employeeId); ga.addParam('sysparm_current_sysid', g_form.getUniqueValue()); // !!! WARNING: Synchronous call - Blocks browser !!! ga.getXMLWait(); var isDuplicate = ga.getAnswer(); if (isDuplicate == 'true') { g_form.addErrorMessage('Uniqueness Error: Employee ID "' + employeeId + '" is already in use.'); g_form.showFieldMsg(fieldName, 'This Employee ID is already assigned.', 'error'); return false; // Prevent submission } else if (isDuplicate == 'false') { g_form.hideFieldMsg(fieldName, true); // Clear previous error if now unique return true; // Allow submission } else { // Error during check g_form.addErrorMessage('Could not verify Employee ID uniqueness. Submission prevented.'); return false; } // Reminder: Asynchronous checks in onSubmit are not reliable for blocking submission. // Server-side onBefore Business Rule with Unique checkbox on dictionary is best. }
-
Type:
onSubmit
(Client Script), GlideAjax, Script Include -
Field:
u_employee_id
-
Explanation: This
onSubmit
script uses a synchronousGlideAjax
call (getXMLWait
) (strongly discouraged due to UI blocking) to ask the server (viaAjaxValidationUtils
Script Include) if the enteredu_employee_id
already exists in the specifiedtableName
(sys_user
), excluding the current record if it's an update. If the server responds that a duplicate exists ('true'
), an error message is shown, and submission is blocked. Best Practice: Use the "Unique" checkbox on the field's dictionary entry, possibly combined with anonBefore
Business Rule for more complex scenarios or better error messaging.
43. Display Notifications for Users
Scenario: When an Incident form loads, display an informational message at the top if the "Caller" (caller_id
) has any other active P1 Incidents.
Solution: Use an onLoad
Client Script with GlideAjax
.
-
Script Include (
AjaxIncidentUtils
):var AjaxIncidentUtils = Class.create(); AjaxIncidentUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, { checkActiveP1Incidents: function() { var callerId = this.getParameter('sysparm_caller_id'); var currentIncidentSysId = this.getParameter('sysparm_current_incident_sysid'); if (!callerId) { return '0'; // No caller specified } var incidentGr = new GlideRecord('incident'); incidentGr.addQuery('caller_id', callerId); incidentGr.addQuery('priority', '1'); // Priority 1 incidentGr.addQuery('active', true); // Active incidents // Exclude the incident currently being viewed if (currentIncidentSysId && currentIncidentSysId != '-1') { incidentGr.addQuery('sys_id', '!=', currentIncidentSysId); } incidentGr.query(); return incidentGr.getRowCount().toString(); // Return the count as a string }, type: 'AjaxIncidentUtils' });
-
Client Script (
onLoad
):function onLoad() { var callerId = g_form.getValue('caller_id'); var currentIncidentSysId = g_form.getUniqueValue(); if (!callerId) { return; // No caller, nothing to check } // Asynchronous call - suitable for onLoad informational messages var ga = new GlideAjax('AjaxIncidentUtils'); ga.addParam('sysparm_name', 'checkActiveP1Incidents'); ga.addParam('sysparm_caller_id', callerId); ga.addParam('sysparm_current_incident_sysid', currentIncidentSysId); ga.getXML(handleP1CheckResponse); // Use async call } function handleP1CheckResponse(response) { var answer = response.responseXML.documentElement.getAttribute("answer"); try { var count = parseInt(answer, 10); if (!isNaN(count) && count > 0) { // Display notification if other P1 incidents exist var message = 'Info: This caller currently has ' + count + ' other active P1 Incident(s).'; g_form.addInfoMessage(message); // Optionally, make priority field flash or something similar (more complex) } } catch(e) { jslog('Error parsing P1 incident count: ' + e); } }
-
Type:
onLoad
(Client Script), GlideAjax, Script Include -
Explanation: The
onLoad
script gets the caller's sys_id. It then makes an asynchronousGlideAjax
call (suitable here as it doesn't need to block anything) to theAjaxIncidentUtils
Script Include. The server-side function queries the Incident table for active P1 incidents for that caller (excluding the current one) and returns the count. The client-side callback function (handleP1CheckResponse
) receives the count. If the count is greater than zero, it displays an informational message at the top of the form usingg_form.addInfoMessage()
.
44. Validate Alphanumeric Input
Scenario: Ensure a field like "Asset Tag" (asset_tag
) contains only letters (a-z, A-Z) and numbers (0-9).
Solution: Use an onChange
Client Script.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
g_form.hideFieldMsg('asset_tag', true); // Clear msg if empty
return;
}
var fieldName = 'asset_tag';
// Regular expression for alphanumeric characters only.
// ^ marks the beginning of the string, $ marks the end.
// + means one or more occurrences.
var alphanumericPattern = /^[a-zA-Z0-9]+$/;
// Clear previous message
g_form.hideFieldMsg(fieldName, true);
// Test the entire string against the pattern
if (!alphanumericPattern.test(newValue)) {
// Value contains non-alphanumeric characters
g_form.showFieldMsg(fieldName, 'Asset Tag must contain only letters and numbers.', 'error');
// Optional: Clear invalid input
// g_form.setValue(fieldName, oldValue); // Revert to previous value
}
}
-
Type:
onChange
-
Field:
asset_tag
-
Explanation: This script triggers when the
asset_tag
field changes. It uses a regular expressionalphanumericPattern
that anchors to the start (^
) and end ($
) of the string and allows only characters in the ranges a-z, A-Z, and 0-9, occurring one or more times (+
). If thenewValue
does not match this pattern completely, an error message is displayed.
45. Autofill Fields Based on Selected User
Scenario: When a user is selected in a reference field named "Assigned To" (assigned_to
), automatically populate the "Assignment Group" (assignment_group
) with the user's primary group and "Manager" (u_assigned_manager
) with the user's manager.
Solution: Use an onChange
Client Script on assigned_to
.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
// Clear related fields if 'assigned_to' is cleared
if (newValue === '') {
g_form.setValue('assignment_group', '');
g_form.setValue('u_assigned_manager', '');
}
return;
}
// Use getReference with a callback to fetch related user data
g_form.getReference('assigned_to', function(userRecord) {
// This function executes when the user record data is available
if (userRecord) {
// Populate Assignment Group (assuming user has a primary group field, e.g., 'u_primary_group')
// Or perhaps use their default group or department group logic here
// Example: Setting based on user's department (if group = department)
// if (userRecord.department) {
// g_form.setValue('assignment_group', userRecord.department); // Assuming dept maps to a group
// }
// More commonly, you might need GlideAjax to find the user's primary group
// based on sys_user_grmember table if not directly on user record.
// For this example, let's assume a direct field exists or we clear it
if (userRecord.u_primary_group) { // Replace with actual field if it exists
g_form.setValue('assignment_group', userRecord.u_primary_group);
} else {
// Clear if no primary group found directly
g_form.setValue('assignment_group', '');
// Consider GlideAjax here for complex group lookups
}
// Populate Manager field (standard 'manager' field on sys_user)
if (userRecord.manager) {
g_form.setValue('u_assigned_manager', userRecord.manager);
} else {
g_form.setValue('u_assigned_manager', ''); // Clear if no manager
}
} else {
// Handle case where user record couldn't be retrieved
g_form.setValue('assignment_group', '');
g_form.setValue('u_assigned_manager', '');
}
});
}
-
Type:
onChange
-
Field:
assigned_to
(Reference to User table) -
Explanation: When the
assigned_to
field changes, this script usesg_form.getReference()
with a callback. Once the selected user's record (userRecord
) is fetched, the callback function executes. It attempts to set theassignment_group
(this part might need adjustment based on how primary groups are stored/determined - potentially requiring GlideAjax for membership checks) and sets theu_assigned_manager
field using the standardmanager
field from the user record. Fields are cleared if theassigned_to
field is emptied or data isn't found.
46. Prevent Invalid File Formats in Attachments (Client-Side Indication)
Scenario: Warn the user immediately if they try to attach a file that is not a PDF or DOCX file. Note: Server-side validation is required for actual enforcement.
Solution: Similar to Scenario 13, this is difficult with standard Platform UI Client Scripts. It requires access to the file properties before upload, typically achieved via custom UI/Widgets or potentially fragile DOM manipulation. Server-side validation on sys_attachment
is the reliable method.
Custom UI/Widget Approach (Conceptual):
// This code would typically be part of a custom UI element/widget's script,
// NOT a standard Platform UI Client Script.
// Assuming 'fileInput' is an HTML file input element <input type="file" id="fileInput">
var fileInput = document.getElementById('fileInput');
fileInput.onchange = function() {
if (this.files.length > 0) {
var file = this.files[0];
var allowedExtensions = /(\.pdf|\.docx)$/i; // Case-insensitive check for .pdf or .docx
if (!allowedExtensions.exec(file.name)) {
alert('Error: Invalid file type. Only PDF and DOCX files are allowed.');
// Clear the file input to prevent upload
this.value = null;
} else {
// Proceed with attaching/uploading the file
console.log('File type is acceptable.');
}
}
};
Server-Side Approach (Reliable - Business Rule):
Create an onBefore
Insert Business Rule on the sys_attachment
table.
-
Condition:
current.table_name == 'YOUR_TABLE_NAME'
(e.g.,incident
) -
Script:
(function executeRule(current, previous /*null when async*/) { var allowedExtensions = ['pdf', 'docx']; // Allowed extensions (lowercase) var fileName = current.file_name.toLowerCase(); var fileExtension = ''; var dotIndex = fileName.lastIndexOf('.'); if (dotIndex > -1 && dotIndex < fileName.length - 1) { fileExtension = fileName.substring(dotIndex + 1); } if (allowedExtensions.indexOf(fileExtension) == -1) { gs.addErrorMessage('Attachment Error: Only files with extensions ' + allowedExtensions.join(', ') + ' are allowed. "' + current.file_name + '" was not attached.'); current.setAbortAction(true); // Prevent the attachment from being saved } })(current, previous);
-
Type: Custom UI Script (client-side indication) or Business Rule (server-side enforcement - Recommended)
-
Explanation: The client-side JavaScript (for custom UIs) checks the
file.name
extension against a list of allowed extensions using a regular expression. If it's not allowed, it shows an alert and clears the input. The recommended server-side Business Rule runs before an attachment record is inserted. It checks thefile_name
extension against the allowed list. If it's not permitted, it adds an error message visible to the user and usescurrent.setAbortAction(true)
to prevent the file from being attached.
47. Validate Input Against Regular Expression
Scenario: Ensure a "Tracking Number" field (u_tracking_number
) matches a specific format, like two letters followed by nine digits (e.g., AA123456789).
Solution: Use an onChange
Client Script.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
g_form.hideFieldMsg('u_tracking_number', true); // Clear msg if empty
return;
}
var fieldName = 'u_tracking_number';
// Regular expression:
// ^ - start of string
// [a-zA-Z]{2} - exactly two letters (case-insensitive)
// \d{9} - exactly nine digits
// $ - end of string
var trackingPattern = /^[a-zA-Z]{2}\d{9}$/;
// Clear previous message
g_form.hideFieldMsg(fieldName, true);
// Test the input against the pattern
if (!trackingPattern.test(newValue)) {
g_form.showFieldMsg(fieldName, 'Invalid format. Tracking Number must be 2 letters followed by 9 digits (e.g., AA123456789).', 'error');
}
}
-
Type:
onChange
-
Field:
u_tracking_number
-
Explanation: This script triggers when the tracking number field changes. It defines a regular expression
trackingPattern
that precisely matches the format of two letters followed by nine digits. If thenewValue
doesn't conform to this pattern, it displays a specific error message guiding the user on the correct format.
48. Dynamically Adjust Field Labels
Scenario: Change the label of the "Assignment Group" field from "Assignment Group" to "Resolver Group" if the "Category" selected is "Software".
Solution: Use an onChange
Client Script on category
. (DOM manipulation - use cautiously).
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) {
// Handle initial label on load as well
setAssignmentGroupLabel(g_form.getValue('category'));
return;
}
setAssignmentGroupLabel(newValue);
}
// Separate function to set the label
function setAssignmentGroupLabel(categoryValue) {
var fieldName = 'assignment_group';
var defaultLabel = 'Assignment Group'; // Get default from system if possible
var softwareLabel = 'Resolver Group';
try {
// Find the label element for the field
var controlElement = g_form.getControl(fieldName);
if (controlElement && controlElement.id) {
// Use $$ selector to find label by 'for' attribute
var labelElement = $$('label[for="' + controlElement.id + '"]')[0];
if (labelElement) {
// Check the category and set the label text
if (categoryValue == 'software') { // Use actual value for 'Software'
labelElement.textContent = softwareLabel;
} else {
labelElement.textContent = defaultLabel;
}
} else {
jslog('Could not find label for ' + fieldName);
}
} else {
jslog('Could not find control for ' + fieldName);
}
} catch (e) {
jslog('Error changing label for ' + fieldName + ': ' + e);
}
}
// Also need onLoad script to set the label correctly when form first loads
function onLoad() {
setAssignmentGroupLabel(g_form.getValue('category'));
}
-
Type:
onChange
(oncategory
),onLoad
-
Explanation: A function
setAssignmentGroupLabel
is created to handle the label change logic. This function finds the label element associated with theassignment_group
field (using DOM selectors, which can be fragile). Based on the passedcategoryValue
, it changes thetextContent
of the label element to either "Resolver Group" or back to the default "Assignment Group". This function is called from anonChange
script on thecategory
field and also from anonLoad
script to ensure the label is correct when the form initially loads. Caution: Relies on DOM structure/selectors. Dynamic labels can sometimes confuse users or automated processes. Consider if alternative field names or form layouts based on category (using Form Design/Views) might be better.
49. Display Contextual Help Text
Scenario: Display persistent help text below the "Configuration Item" (cmdb_ci
) field, explaining its purpose.
Solution: Use an onLoad
Client Script with g_form.showFieldMsg()
.
function onLoad() {
var fieldName = 'cmdb_ci';
var helpText = 'Select the specific hardware, software, or service that is affected or related to this record.';
var messageType = 'info'; // Use 'info' for help text (blue icon)
var persistent = true; // Make the message stay until explicitly hidden
// Display the help message below the field
g_form.showFieldMsg(fieldName, helpText, messageType, persistent);
// Note: If the field is initially hidden and shown later via UI Policy/Script,
// this onLoad script might fire before the field is visible.
// In such cases, you might need to call showFieldMsg again when the field becomes visible.
}
-
Type:
onLoad
-
Explanation: This script runs when the form loads. It uses
g_form.showFieldMsg()
to display thehelpText
directly below thecmdb_ci
field. ThemessageType
is set toinfo
(usually displays with a blue 'i' icon), and the last parameterpersistent
is set totrue
(or omitted, as default is true) so the message doesn't disappear automatically. This is a clean way to add contextual help without DOM manipulation. Standard "Hint" text configured in the dictionary entry is another way to achieve this.
50. Automatically Update Related Records (Client-Side Trigger)
Scenario: When the "State" of a Problem record (problem
) is changed to "Resolved", prompt the user if they want to resolve all related active Incidents associated with that Problem. If confirmed, trigger a server-side action.
Solution: Use an onChange
Client Script on state
combined with GlideAjax
to perform the server-side update.
-
Client Script (
onChange
onstate
):function onChange(control, oldValue, newValue, isLoading, isTemplate) { if (isLoading || isTemplate) { return; } var resolvedStateValue = '4'; // Replace with your actual 'Resolved' state value for Problem var problemSysId = g_form.getUniqueValue(); // Check if the state is changed *to* Resolved if (newValue == resolvedStateValue && oldValue != newValue) { // Ask for confirmation var confirmation = confirm("This Problem is being marked as Resolved. Do you want to automatically resolve all related active Incidents?"); if (confirmation) { // Show progress indicator (optional) g_form.addInfoMessage('Attempting to resolve related incidents...', 'resolve_incidents_progress'); // Call GlideAjax to trigger server-side processing var ga = new GlideAjax('ProblemUtils'); // Your Script Include name ga.addParam('sysparm_name', 'resolveRelatedIncidents'); ga.addParam('sysparm_problem_id', problemSysId); ga.getXML(handleIncidentResolveResponse); // Async call } } } function handleIncidentResolveResponse(response) { // Hide progress indicator g_form.hideFieldMsg('resolve_incidents_progress', true); var answer = response.responseXML.documentElement.getAttribute("answer"); try { var result = JSON.parse(answer); // Expecting {success: true/false, count: N, message: ''} if (result.success) { g_form.addInfoMessage(result.count + ' related active Incident(s) have been resolved.'); // Optional: Refresh related lists if needed // GlideList2.get('incident.problem_id').refresh(); // Adjust related list name } else { g_form.addErrorMessage('Failed to resolve related incidents: ' + (result.message || 'Unknown error.')); } } catch(e) { g_form.addErrorMessage('Error processing incident resolution response: ' + e); jslog('Incident resolution response error: ' + e); } }
-
Script Include (
ProblemUtils
):var ProblemUtils = Class.create(); ProblemUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, { resolveRelatedIncidents: function() { var problemId = this.getParameter('sysparm_problem_id'); var resolvedCount = 0; var result = { success: false, count: 0, message: '' }; if (!problemId) { result.message = 'Problem ID not provided.'; return JSON.stringify(result); } // Find the Problem record to get resolution details (optional) var probGr = new GlideRecord('problem'); var resolutionCode = ''; var resolutionNotes = ''; if (probGr.get(problemId)) { resolutionCode = probGr.getValue('close_code'); // Example field resolutionNotes = 'Resolved via Problem ' + probGr.getValue('number') + '.\n' + probGr.getValue('close_notes'); // Example field } else { result.message = 'Problem record not found.'; return JSON.stringify(result); } // Find related active incidents var incidentGr = new GlideRecord('incident'); incidentGr.addQuery('problem_id', problemId); incidentGr.addActiveQuery(); // Filter for active incidents incidentGr.query(); while (incidentGr.next()) { try { // Update incident state and resolution info incidentGr.setValue('state', '6'); // Assuming '6' is Resolved state for Incident incidentGr.setValue('incident_state', '6'); // Some systems use incident_state too incidentGr.setValue('resolved_by', gs.getUserID()); // User who triggered action incidentGr.setValue('resolved_at', new GlideDateTime()); incidentGr.setValue('close_code', resolutionCode); // Set resolution code from Problem incidentGr.setValue('close_notes', resolutionNotes); // Set resolution notes incidentGr.comments = 'Incident resolved automatically due to resolution of Problem ' + probGr.getValue('number') + '.'; incidentGr.update(); resolvedCount++; } catch (e) { gs.error('Failed to resolve Incident ' + incidentGr.getValue('number') + ' for Problem ' + probGr.getValue('number') + ': ' + e.getMessage()); // Continue trying to resolve others } } result.success = true; result.count = resolvedCount; result.message = resolvedCount + ' incidents processed.'; return JSON.stringify(result); }, type: 'ProblemUtils' });
-
Type:
onChange
(Client Script), GlideAjax, Script Include -
Field:
state
(on Problem form) -
Explanation: When the Problem
state
changes to the 'Resolved' value, theonChange
Client Script prompts the user for confirmation. If confirmed, it calls aGlideAjax
function (resolveRelatedIncidents
) in theProblemUtils
Script Include, passing the Problem's sys_id. The server-side function finds the Problem record (to potentially copy resolution notes), then finds all related active Incidents. It iterates through these Incidents, updates their state to 'Resolved', populates resolution fields (like code, notes, resolved by/at), and adds a comment. It returns a JSON object indicating success and the number of incidents resolved. The client-side callback function displays an appropriate message to the user based on the response.
51. Calculate and Display Age Based on Birthdate
Scenario: When a user selects a date in the "Date of Birth" field (u_dob
), automatically calculate and display their current age in a read-only "Age" field (u_age
).
Solution: Use an onChange
Client Script on u_dob
.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
g_form.setValue('u_age', ''); // Clear age if DOB is cleared
return;
}
var dobFieldName = 'u_dob';
var ageFieldName = 'u_age';
try {
// --- Using modern GlideDate API (Recommended) ---
var dobDate = new GlideDate();
dobDate.setDisplayValue(newValue); // Assumes newValue is in user display format
var today = new GlideDate();
if (dobDate.after(today)) {
g_form.showFieldMsg(dobFieldName, 'Date of Birth cannot be in the future.', 'error');
g_form.setValue(ageFieldName, '');
g_form.setValue(dobFieldName, ''); // Clear invalid DOB
return;
} else {
g_form.hideFieldMsg(dobFieldName, true); // Clear error if valid
}
// Calculate age (simple year difference, adjust for birthday this year)
var age = today.getYear() - dobDate.getYear();
var monthDiff = today.getMonth() - dobDate.getMonth(); // GlideDate months are 1-12
if (monthDiff < 0 || (monthDiff === 0 && today.getDayOfMonth() < dobDate.getDayOfMonth())) {
age--; // Subtract a year if birthday hasn't occurred yet this year
}
// Set the age field (assuming it's an integer or string field)
g_form.setValue(ageFieldName, age >= 0 ? age : 0); // Ensure age is not negative
// --- Using older JavaScript Date parsing (More complex due to formats/timezones) ---
/*
var dob = new Date(getDateFromFormat(newValue, g_user_date_format)); // Needs helper function
var today = new Date();
// Basic validation
if (isNaN(dob.getTime()) || dob > today) {
g_form.showFieldMsg(dobFieldName, 'Please enter a valid past date for Date of Birth.', 'error');
g_form.setValue(ageFieldName, '');
if (dob > today) g_form.setValue(dobFieldName, ''); // Clear future date
return;
} else {
g_form.hideFieldMsg(dobFieldName, true);
}
var age = today.getFullYear() - dob.getFullYear();
var m = today.getMonth() - dob.getMonth(); // JS months are 0-11
if (m < 0 || (m === 0 && today.getDate() < dob.getDate())) {
age--;
}
g_form.setValue(ageFieldName, age >= 0 ? age : 0);
*/
} catch(e) {
jslog('Error calculating age: ' + e);
g_form.setValue(ageFieldName, ''); // Clear age on error
g_form.showFieldMsg(dobFieldName, 'Could not calculate age from the provided date.', 'error');
}
}
// Assume getDateFromFormat exists if using older JS Date approach (see Q5)
// Make sure the 'u_age' field is set to read-only via Dictionary or UI Policy.
-
Type:
onChange
-
Field:
u_dob
-
Explanation: This script triggers when the
u_dob
field changes. It uses the recommendedGlideDate
client-side API to parse the selected date and get the current date. It validates that the DOB is not in the future. It then calculates the difference in years and adjusts downwards by one year if the birthday has not yet occurred in the current calendar year. The calculated age is then populated into theu_age
field usingg_form.setValue()
. Theu_age
field should ideally be made read-only. The commented-out section shows the older, more complex way using JavaScriptDate
objects, which requires careful handling of date formats.
52. Prevent Submission If Conditions Not Met
Scenario: This is a repeat of Scenario 4. Prevent submitting a form if certain conditions are not met (e.g., assignment_group
empty when state
is "In Progress").
Solution: Use an onSubmit
Client Script.
function onSubmit() {
// Example Condition: Assignment Group required if state is Active/In Progress/etc.
var state = g_form.getValue('state');
var assignmentGroup = g_form.getValue('assignment_group');
// Define states that require an assignment group (use actual values)
var activeStates = ['1', '2', '-5']; // Example: New, Active, Pending
// Check if the current state is one that requires the group AND the group is empty
if (activeStates.indexOf(state) > -1 && assignmentGroup == '') {
// Condition not met
g_form.addErrorMessage('Assignment Group is required when the state is ' + g_form.getDisplayValue('state') + '.');
g_form.flash('assignment_group', '#FFFACD', 0); // Flash the field yellow
g_form.setMandatory('assignment_group', true); // Mark as mandatory visually
return false; // Prevent submission
}
// Example Condition 2: Justification required if priority is P1
var priority = g_form.getValue('priority');
var justification = g_form.getValue('justification'); // Assuming a justification field exists
if (priority == '1' && justification.trim() == '') {
g_form.addErrorMessage('Justification is required for P1 priority.');
g_form.flash('justification', '#FFFACD', 0);
g_form.setMandatory('justification', true);
return false; // Prevent submission
}
// Add more conditions as needed...
// If all conditions are met, allow submission
// Optional: Clear any mandatory flags set above if conditions are now met
g_form.setMandatory('assignment_group', false);
g_form.setMandatory('justification', false);
return true;
}
-
Type:
onSubmit
-
Explanation: This script runs just before the form submits. It checks one or more conditions. If any condition required for submission is not met:
- An error message is displayed using
g_form.addErrorMessage()
. - The relevant field(s) might be flashed using
g_form.flash()
or marked mandatory usingg_form.setMandatory()
. - The script returns
false
, which stops the form submission process. If all conditions pass, the script eventually returnstrue
, allowing the form to be submitted.
- An error message is displayed using
53. Auto-capitalize First Letter
Scenario: Automatically capitalize the first letter of each word entered into the "Name" field (name
).
Solution: Use an onChange
Client Script.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
return;
}
var fieldName = 'name';
// Function to capitalize the first letter of each word
function titleCase(str) {
// Handle potential null/undefined input
if (!str) return '';
// Convert the whole string to lower case first to handle mixed case input
return str.toLowerCase().split(' ').map(function(word) {
// Capitalize the first letter, leave rest as is (after lowercasing)
return (word.charAt(0).toUpperCase() + word.slice(1));
}).join(' '); // Join the words back together with spaces
}
// Apply the title case function to the new value
var correctedValue = titleCase(newValue);
// Only update the field if the corrected value is different from the current input
// This prevents infinite loops if the user types already capitalized text.
if (correctedValue !== newValue) {
// Use g_form.setValue cautiously in onChange to avoid cursor jumping/loops.
// It might be better to do this validation onSubmit or using different event handling.
// However, for simple capitalization:
g_form.setValue(fieldName, correctedValue);
}
}
-
Type:
onChange
-
Field:
name
-
Explanation: This script triggers when the
name
field changes. It defines a helper functiontitleCase
that splits the input string into words, capitalizes the first letter of each word (after converting the word to lowercase first for consistency), and joins them back. It applies this function to thenewValue
. To prevent potential issues like the cursor jumping or infinite loops, it only callsg_form.setValue()
to update the field if thecorrectedValue
is actually different from the user's direct input (newValue
). Note: Modifying user input directly as they type can sometimes lead to usability issues.
54. Validate Checkbox Selections
Scenario: On a form with multiple checkboxes representing required training modules (e.g., u_training_module_a
, u_training_module_b
, u_training_module_c
), ensure that at least one module is checked before the form can be submitted.
Solution: Use an onSubmit
Client Script.
function onSubmit() {
// List of checkbox field names to check
var checkboxFields = ['u_training_module_a', 'u_training_module_b', 'u_training_module_c'];
var isAtLeastOneChecked = false;
// Loop through the checkbox field names
for (var i = 0; i < checkboxFields.length; i++) {
var fieldName = checkboxFields[i];
// g_form.getValue() returns 'true' or 'false' for checkboxes
if (g_form.getValue(fieldName) == 'true') {
isAtLeastOneChecked = true;
break; // Found one checked, no need to check further
}
}
// Check if none were checked
if (!isAtLeastOneChecked) {
// Display an error message
g_form.addErrorMessage('Please select at least one required training module before submitting.');
// Optional: Highlight the section or first checkbox (can be tricky)
// g_form.flash(checkboxFields[0], '#FFFACD', 0); // Flash first checkbox
return false; // Prevent submission
}
// At least one checkbox is checked, allow submission
return true;
}
-
Type:
onSubmit
-
Explanation: This script runs before submission. It defines an array
checkboxFields
containing the names of the relevant checkbox fields. It iterates through this array, checking the value of each checkbox usingg_form.getValue()
. If it finds any checkbox whose value is'true'
, it sets a flagisAtLeastOneChecked
to true and stops checking. After the loop, if the flag is still false (meaning none were checked), it displays an error message and returnsfalse
to prevent submission. Otherwise, it returnstrue
.
No Comments