Skip to main content

Client Script Use-case Scenario Questions

  1. How would you use a Client Script to validate user input in a form field?
  2. Can you explain how to dynamically hide or show form fields based on user selections using a Client Script?
  3. Describe a scenario where you would use a Client Script to auto-populate a field based on another field's value.
  4. How do you prevent users from submitting a form until certain conditions are met using Client Scripts?
  5. Explain how to use a Client Script to perform client-side validation for a date field to ensure it's in the future.
  6. Provide an example of using a Client Script to display a confirmation dialog before submitting a form.
  7. Describe a scenario where you would use a Client Script to restrict certain users from editing specific fields.
  8. How would you implement a Client Script to enforce character limits in a text area field?
  9. Explain how to use Client Scripts to dynamically set the value of a choice field based on a user's role.
  10. Describe a scenario where you would use a Client Script to automatically calculate the total cost based on item quantity and price.
  11. Can you provide an example of using a Client Script to display a custom error message when certain conditions are met?
  12. How would you use a Client Script to automatically update the priority field based on the selected category?
  13. Explain how to use a Client Script to prevent users from adding attachments larger than a specified size.
  14. Describe a scenario where you would use a Client Script to dynamically change the background color of a form field based on its value.
  15. Provide an example of using a Client Script to validate email addresses entered in a form field.
  16. How do you use a Client Script to enforce mandatory fields before submitting a form?
  17. Describe a scenario where you would use a Client Script to perform an AJAX call to retrieve additional data for a form field.
  18. Explain how to use Client Scripts to disable form fields based on certain conditions.
  19. How would you implement a Client Script to display a warning message when a certain date is approaching?
  20. Provide an example of using a Client Script to restrict access to a form based on the user's department.
  21. Describe a scenario where you would use a Client Script to validate phone numbers entered in a form field.
  22. Can you explain how to use a Client Script to automatically populate location information based on GPS coordinates?
  23. How do you use Client Scripts to dynamically update the options available in a choice field based on another field's value?
  24. Describe a scenario where you would use a Client Script to display a warning when a form has been idle for too long.
  25. Explain how to use Client Scripts to validate numeric input within a specified range.
  26. Provide an example of using a Client Script to hide certain form sections based on user roles.
  27. How would you implement a Client Script to display a progress indicator while submitting a form?
  28. Describe a scenario where you would use a Client Script to validate URL formats entered in a form field.
  29. Can you explain how to use a Client Script to automatically calculate due dates based on selected options?
  30. Explain how to use Client Scripts to prevent users from submitting duplicate records.
  31. Describe a scenario where you would use a Client Script to perform real-time validation of a credit card number.
  32. Provide an example of using a Client Script to auto-populate user information based on the logged-in user.
  33. How do you use Client Scripts to dynamically update the available choices in a dependent choice field?
  34. Describe a scenario where you would use a Client Script to display tooltips for form fields.
  35. Explain how to use Client Scripts to restrict past dates from being selected in a date field.
  36. Provide an example of using a Client Script to limit the number of characters entered in a text field.
  37. How would you implement a Client Script to automatically populate form fields based on data from an external source?
  38. Describe a scenario where you would use a Client Script to validate input against a predefined list of values.
  39. Can you explain how to use a Client Script to dynamically adjust form field visibility based on screen size?
  40. Explain how to use Client Scripts to prevent users from submitting forms outside of business hours.
  41. Describe a scenario where you would use a Client Script to validate special characters entered in a form field.
  42. Provide an example of using a Client Script to enforce unique values in a form field.
  43. How do you use Client Scripts to display notifications for users when specific conditions are met?
  44. Describe a scenario where you would use a Client Script to validate alphanumeric input in a form field.
  45. Can you explain how to use a Client Script to autofill in form fields based on the selected user?
  46. Explain how to use Client Scripts to prevent users from submitting forms with invalid file formats in attachments.
  47. Describe a scenario where you would use a Client Script to validate user input against a regular expression pattern.
  48. Provide an example of using a Client Script to dynamically adjust form field labels based on user selections.
  49. How do you use Client Scripts to display contextual help text for form fields?
  50. Describe a scenario where you would use a Client Script to automatically update related records based on form input.
  51. Explain how to use Client Scripts to calculate and display the age based on a selected birthdate.
  52. Provide an example of using a Client Script to prevent users from submitting forms if certain conditions are not met.
  53. How would you implement a Client Script to automatically capitalize the first letter of input in a text field?
  54. 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 the newValue contains only letters and numbers. If not, it displays an error message using g_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 the newValue matches the value representing "Rejected". If it does, the u_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 uses g_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 the assignment_group field is empty. If both conditions are true, it displays an error message using g_form.addErrorMessage() and returns false to halt the submission process. Otherwise, it returns true, 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 JavaScript Date object (handling user date formats is crucial here, potentially requiring GlideAjax 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() returns false), the script returns false, stopping the submission. Otherwise (confirm() returns true), the script returns true, 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 the itil_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 the priority field read-only using g_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 the newValue. If it exceeds the maxLength, 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 the major_incident_manager role. If they do, it sets the impact field's value to 1 (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 (on quantity and u_price_per_item), onLoad
  • Fields: quantity, u_price_per_item
  • Explanation: Separate onChange scripts are created for quantity and u_price_per_item. Both call a common function calculateTotalCost. This function retrieves the integer value of quantity and the decimal value of price, calculates the product, and sets the u_total_cost field, formatting it to two decimal places. An onLoad 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 (or onChange on subcategory and asset_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 uses g_form.showFieldMsg() to display a targeted error message directly under the asset_tag field and prevents submission by returning false.

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 the newValue corresponds to "Network". If it does, it automatically sets the priority field's value to 2 (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 the file.size against the maxSizeBytes. 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 using g_form.getControl(). If the new value represents "High", it sets the backgroundColor style of the element to light red. Otherwise, it resets the background color. An onLoad 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 the newValue roughly matches a standard email structure. If the format is invalid, it displays an error message using g_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 or description are empty (using trim() 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:

  1. 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'
      });
      
  2. Create an onChange Client Script on cmdb_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 a GlideAjax call to the AjaxClientUtils Script Include, passing the selected CI's sys_id. The Script Include (getCIDetails function) runs server-side, queries the cmdb_ci table for the given sys_id, retrieves the support_group and business_criticality, and returns them as a JSON string. The Client Script's callback function (handleResponse) receives this JSON, parses it, and uses g_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 the newValue of the state field matches the values for "Closed" or "Cancelled". If it does, it sets the assignment_group and assigned_to fields to disabled using g_form.setDisabled(fieldName, true). Otherwise, it enables them (g_form.setDisabled(fieldName, false)). The onLoad 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 the follow_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 using g_form.showFieldMsg() or g_form.addInfoMessage(). This function is called by an onLoad script to check when the form loads and an onChange 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 uses GlideAjax 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.

  1. Client Script (onChange on u_latitude and u_longitude): Trigger an AJAX call when coordinates change (after some debounce potentially).
  2. 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.
  3. 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 the cmn_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 the location 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 (on category), potentially onLoad
  • Fields: category, subcategory
  • Explanation: When the category field changes, the script first clears all existing options from the subcategory field using g_form.clearOptions(). It then adds a default "-- None --" option. Based on the newValue of the category, it uses g_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 using setTimeout. It defines a resetTimer 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, the showIdleWarning function is executed, displaying a warning message (using GlideModal for a better experience than alert). 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 the newValue into an integer using parseInt(). It then checks if the result is NaN (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 to maxVal). 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 using g_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 because onSubmit must return true or false synchronously. Asynchronous operations like GlideAjax 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 calls g_form.submit() if valid) or handled server-side with a Business Rule. The example code highlights these limitations.

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 with http:// or https:// 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.

  1. 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'
    });
    
  2. Client Script (onChange on priority):

    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 a GlideAjax function, passing the priority and duration. The server-side Script Include uses GlideDateTime to add the duration (potentially using DurationCalculator 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 the due_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.

  1. 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'
    });
    
  2. 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 the serial_number. It should call GlideAjax 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, but onSubmit needs a synchronous true or false 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.

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 the requested_for field. To set the department, it uses g_form.getReference on the just-populated requested_for field. The callback function receives the user record details and sets the department field using the department sys_id from the user record. Using getReference is often simpler than GlideAjax 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 the subcategory options. It then uses GlideAjax to call a Script Include (getSubcategoryChoices). The Script Include queries the sys_choice table (or another source) for choices associated with the selected category (using the dependent_value field on sys_choice). It returns the relevant choices as a JSON array. The client-side callback function parses the JSON and uses g_form.addOption() to populate the subcategory 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 the cmdb_ci field. It does this by first getting the field's control (input/select element) using g_form.getControl(), then finding the label whose for attribute matches the control's id (using the $$ selector, common in ServiceNow's client environment). If found, it sets the label's title attribute to the desired tooltipText. Browsers render the title 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 modern GlideDate 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 exceeds maxLength. If it does, it displays an informational message warning the user. Alternatively (commented out), it could actively truncate the input using substring() and g_form.setValue(). It's generally better UX to just warn the user. Setting the max_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.

  1. 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).
  2. 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'
    });
    
  3. Client Script (onChange on u_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 a GlideAjax call. The Script Include receives the IP address. It then uses RESTMessageV2 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 the u_ip_city and u_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 entered newValue (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 set display: none on the elements associated with the field (label, input, container). The Client Script approach uses window.innerWidth to detect screen size on load and during resize events (with debouncing). It uses g_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.

  1. 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.
  2. 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'
    });
    
  3. 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 synchronous GlideAjax call (getXMLWait) - which is bad practice as it freezes the user's browser - to call the AjaxScheduleUtils Script Include. The Script Include takes the sys_id of a pre-defined Schedule record, gets the current server time using GlideDateTime, and uses GlideSchedule.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 on getXMLWait, 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 expression disallowedCharsPattern that matches if any of the specified characters (!, #, $, %) are present in the newValue. If the test() 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.

  1. 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'
    });
    
  2. 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 synchronous GlideAjax call (getXMLWait) (strongly discouraged due to UI blocking) to ask the server (via AjaxValidationUtils Script Include) if the entered u_employee_id already exists in the specified tableName (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 an onBefore 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.

  1. 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'
    });
    
  2. 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 asynchronous GlideAjax call (suitable here as it doesn't need to block anything) to the AjaxIncidentUtils 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 using g_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 expression alphanumericPattern 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 the newValue 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 uses g_form.getReference() with a callback. Once the selected user's record (userRecord) is fetched, the callback function executes. It attempts to set the assignment_group (this part might need adjustment based on how primary groups are stored/determined - potentially requiring GlideAjax for membership checks) and sets the u_assigned_manager field using the standard manager field from the user record. Fields are cleared if the assigned_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 the file_name extension against the allowed list. If it's not permitted, it adds an error message visible to the user and uses current.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 the newValue 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 (on category), onLoad
  • Explanation: A function setAssignmentGroupLabel is created to handle the label change logic. This function finds the label element associated with the assignment_group field (using DOM selectors, which can be fragile). Based on the passed categoryValue, it changes the textContent of the label element to either "Resolver Group" or back to the default "Assignment Group". This function is called from an onChange script on the category field and also from an onLoad 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 the helpText directly below the cmdb_ci field. The messageType is set to info (usually displays with a blue 'i' icon), and the last parameter persistent is set to true (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.

  1. Client Script (onChange on state):
    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);
        }
    }
    
  2. 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, the onChange Client Script prompts the user for confirmation. If confirmed, it calls a GlideAjax function (resolveRelatedIncidents) in the ProblemUtils 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 recommended GlideDate 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 the u_age field using g_form.setValue(). The u_age field should ideally be made read-only. The commented-out section shows the older, more complex way using JavaScript Date 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:
    1. An error message is displayed using g_form.addErrorMessage().
    2. The relevant field(s) might be flashed using g_form.flash() or marked mandatory using g_form.setMandatory().
    3. The script returns false, which stops the form submission process. If all conditions pass, the script eventually returns true, allowing the form to be submitted.

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 function titleCase 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 the newValue. To prevent potential issues like the cursor jumping or infinite loops, it only calls g_form.setValue() to update the field if the correctedValue 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 using g_form.getValue(). If it finds any checkbox whose value is 'true', it sets a flag isAtLeastOneChecked to true and stops checking. After the loop, if the flag is still false (meaning none were checked), it displays an error message and returns false to prevent submission. Otherwise, it returns true.