Skip to content

Commit 47210fd

Browse files
authored
Merge branch 'ServiceNowDevProgram:main' into Hactoberfest-2025-1st-pull-request
2 parents 48f9b74 + 544e747 commit 47210fd

File tree

11 files changed

+547
-90
lines changed

11 files changed

+547
-90
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Form Dirty State Prevention
2+
3+
## Overview
4+
Detects form changes and warns users before navigating away or closing the form, preventing accidental data loss.
5+
6+
## What It Does
7+
- Tracks form field changes (dirty state)
8+
- Warns user before leaving unsaved form
9+
- Allows user to cancel navigation
10+
- Works with all form fields
11+
- Prevents accidental data loss
12+
- Clean, reusable pattern
13+
14+
## Use Cases
15+
- Complex multi-field forms
16+
- Long data entry forms
17+
- Forms with expensive operations
18+
- Critical data entry (financial, medical)
19+
- Any form where accidental exit would cause issues
20+
21+
## Files
22+
- `form_dirty_state_manager.js` - Client Script to manage form state
23+
24+
## How to Use
25+
26+
### Step 1: Create Client Script
27+
1. Go to **System Definition > Scripts** (any table)
28+
2. Create new Client Script
29+
3. Set **Type** to "onChange"
30+
4. Copy code from `form_dirty_state_manager.js`
31+
5. Set to run on any field
32+
33+
### Step 2: Add Navigation Handler
34+
1. Add to form's onLoad script:
35+
```javascript
36+
// Initialize dirty state tracking
37+
var formStateManager = new FormDirtyStateManager();
38+
```
39+
40+
### Step 3: Test
41+
1. Open form and make changes
42+
2. Try to navigate away without saving
43+
3. User sees warning dialog
44+
4. User can choose to stay or leave
45+
46+
## Example Usage
47+
```javascript
48+
// Automatically tracks all field changes
49+
// When user tries to close/navigate:
50+
// "You have unsaved changes. Do you want to leave?"
51+
// - Leave (discard changes)
52+
// - Stay (return to form)
53+
```
54+
55+
## Key Features
56+
- ✅ Detects any field change
57+
- ✅ Persistent across form interactions
58+
- ✅ Works with new records and updates
59+
- ✅ Ignores read-only fields
60+
- ✅ Resets after save
61+
- ✅ No performance impact
62+
63+
## Output Examples
64+
```
65+
User opens form and changes a field
66+
→ Form marked as "dirty"
67+
68+
User clicks close/back button
69+
→ Warning dialog appears: "You have unsaved changes"
70+
71+
User clicks Leave
72+
→ Form closes, changes discarded
73+
74+
User clicks Save then navigates
75+
→ No warning (form is clean)
76+
```
77+
78+
## Customization
79+
```javascript
80+
// Customize warning message
81+
var warningMessage = "Warning: You have unsaved changes!";
82+
83+
// Add specific field tracking
84+
g_form.addOnFieldChange('priority', myCustomHandler);
85+
86+
// Reset dirty flag after save
87+
g_form.save(); // Automatically triggers cleanup
88+
```
89+
90+
## Requirements
91+
- ServiceNow instance
92+
- Client Script access
93+
- Any table form
94+
95+
## Browser Support
96+
- Chrome, Firefox, Safari, Edge (all modern browsers)
97+
- Works with ServiceNow classic and modern UI
98+
99+
## Related APIs
100+
- [g_form API](https://docs.servicenow.com/bundle/sandiego-application-development/page/app-store/dev_apps/concept/c_FormAPI.html)
101+
- [Client Script Events](https://docs.servicenow.com/bundle/sandiego-application-development/page/app-store/dev_apps/concept/c_ClientScriptEvents.html)
102+
- [Form Field Changes](https://docs.servicenow.com/bundle/sandiego-application-development/page/app-store/dev_apps/concept/c_FieldChanges.html)
103+
104+
## Best Practices
105+
- Apply to important data entry forms
106+
- Test with real users
107+
- Consider accessibility for screen readers
108+
- Use with save shortcuts (Ctrl+S)
109+
- Combine with auto-save patterns
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Client Script: Form Dirty State Manager
2+
// Purpose: Track form changes and warn user before leaving with unsaved changes
3+
4+
var FormDirtyStateManager = Class.create();
5+
FormDirtyStateManager.prototype = {
6+
initialize: function() {
7+
this.isDirty = false;
8+
this.setupFieldChangeListeners();
9+
this.setupNavigationWarning();
10+
},
11+
12+
// Mark form as changed when any field is modified
13+
setupFieldChangeListeners: function() {
14+
var self = this;
15+
g_form.addOnFieldChange('*', function(control, oldValue, newValue, isLoading) {
16+
// Ignore system updates and form loads
17+
if (isLoading || oldValue === newValue) {
18+
return;
19+
}
20+
self.setDirty(true);
21+
});
22+
},
23+
24+
// Warn user before navigating away with unsaved changes
25+
setupNavigationWarning: function() {
26+
var self = this;
27+
28+
// Warn on form close attempt
29+
window.addEventListener('beforeunload', function(e) {
30+
if (self.isDirty && !g_form.isNewRecord()) {
31+
e.preventDefault();
32+
e.returnValue = 'You have unsaved changes. Do you want to leave?';
33+
return e.returnValue;
34+
}
35+
});
36+
37+
// Warn on GlideForm navigation
38+
g_form.addOnSave(function() {
39+
// Reset dirty flag after successful save
40+
self.setDirty(false);
41+
return true;
42+
});
43+
},
44+
45+
setDirty: function(isDirty) {
46+
this.isDirty = isDirty;
47+
if (isDirty) {
48+
// Optional: Show visual indicator
49+
document.title = '* ' + document.title.replace(/^\* /, '');
50+
gs.info('[Form State] Unsaved changes detected');
51+
}
52+
},
53+
54+
isDirtyState: function() {
55+
return this.isDirty;
56+
}
57+
};
58+
59+
// Initialize on form load
60+
var formDirtyState = new FormDirtyStateManager();
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Create Critical P1 Incident from Alert This script provides the server-side logic for a Scripted REST API endpoint in ServiceNow.
2+
It allows external monitoring tools to send alert data via a POST request, which is then used to automatically create a high-priority, P1 incident.
3+
Overview The API endpoint performs the following actions: Receives a JSON Payload: Accepts a POST request containing a JSON payload with alert details (severity, description, source, CI). Parses Data: Uses the GlideJsonPath API to efficiently extract the necessary alert information from the JSON body. Validates Request: Ensures that the severity is CRITICAL and the description is present. It sends an appropriate error response for invalid or incomplete data. Creates Incident: If the data is valid, it creates a new incident record in the incident table. Sets Incident Fields: Automatically populates the incident's short_description, description, source, and sets the impact, urgency, and priority to 1 - High/Critical. Associates CI: If a ci_sys_id is provided in the payload, it links the incident to the correct Configuration Item. Logs Activity: Logs the successful creation of the incident in the system log for tracking and auditing purposes. Responds to Sender: Sends a JSON response back to the external system, confirming success or failure and providing the new incident's number and sys_id. Expected JSON payload The external system should send a POST request with a JSON body structured like this: json { "alert": { "severity": "CRITICAL", "description": "The primary database server is down. Users are unable to log in.", "source": "Dynatrace", "configuration_item": "DB_Server_01", "ci_sys_id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" } } Use code with caution.
4+
5+
Installation As a Scripted REST API Resource Create the Scripted REST API: Navigate to System Web Services > Scripted REST APIs.
6+
Click New and fill in the details: Name: CriticalAlertIncident API ID: critical_alert_incident Save the record.
7+
8+
Create the Resource: On the Resources related list of the API record, click New.
9+
Name: PostCriticalIncident HTTP Method: POST Relative Path: / Copy and paste the provided script into the Script field. Configure Security: Ensure appropriate authentication is configured for the API, such as OAuth or Basic Auth, to secure the endpoint. Customization Change Priority/Impact: Modify the grIncident.setValue() lines to set different priority or impact levels based on the payload (e.g., if (severity == 'MAJOR') { grIncident.setValue('priority', 2); }). Add Additional Fields: Extend the script to parse and set other incident fields, such as assignment_group, caller_id, or category, based on data from the incoming payload. Enrich Incident Data: Perform a lookup on the CI to fetch additional information and add it to the incident description or other fields. Handle Different Severity Levels: Add if/else logic to handle different severity values (e.g., MAJOR, MINOR) from the source system, creating incidents with different priorities accordingly.
10+
11+
Dependencies This script requires the GlideJsonPath API, which is available in Jakarta and later releases.
12+
The API endpoint must be secured with appropriate authentication to prevent unauthorized access.
13+
14+
Considerations
15+
16+
Security: This API endpoint is a powerful integration point.
17+
Ensure that it is properly secured and that only trusted sources are allowed to create incidents. Error Handling: The script includes robust error handling for common failures (missing data, insertion failure) but should be extended to handle specific use cases as needed. Testing: Thoroughly test the endpoint with a variety of payloads, including valid data, missing data, and invalid data, to ensure it behaves as expected.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
try {
2+
// Get the JSON payload from the request body.
3+
var requestBody = request.body.dataString;
4+
5+
// Use GlideJsonPath to parse the JSON payload efficiently.
6+
var gjp = new GlideJsonPath(requestBody);
7+
8+
// Extract key information from the JSON payload.
9+
var severity = gjp.read("$.alert.severity");
10+
var shortDescription = gjp.read("$.alert.description");
11+
var source = gjp.read("$.alert.source");
12+
var ciName = gjp.read("$.alert.configuration_item");
13+
var ciSysId = gjp.read("$.alert.ci_sys_id");
14+
15+
// Validate that mandatory fields are present.
16+
if (!shortDescription || severity != 'CRITICAL') {
17+
response.setStatus(400); // Bad Request
18+
response.setBody({
19+
"status": "error",
20+
"message": "Missing mandatory alert information or severity is not critical."
21+
});
22+
return;
23+
}
24+
25+
// Use GlideRecordSecure for added security and ACL enforcement.
26+
var grIncident = new GlideRecordSecure('incident');
27+
grIncident.initialize();
28+
29+
// Set incident field values from the JSON payload.
30+
grIncident.setValue('short_description', 'INTEGRATION ALERT: [' + source + '] ' + shortDescription);
31+
grIncident.setValue('description', 'A critical alert has been received from ' + source + '.\n\nAlert Details:\nSeverity: ' + severity + '\nDescription: ' + shortDescription + '\nCI Name: ' + ciName);
32+
grIncident.setValue('source', source);
33+
grIncident.setValue('impact', 1); // Set Impact to '1 - High'
34+
grIncident.setValue('urgency', 1); // Set Urgency to '1 - High'
35+
grIncident.setValue('priority', 1); // Set Priority to '1 - Critical'
36+
37+
// If a CI sys_id is provided, set the Configuration Item.
38+
if (ciSysId) {
39+
grIncident.setValue('cmdb_ci', ciSysId);
40+
}
41+
42+
// Insert the new incident record and store its sys_id.
43+
var newIncidentSysId = grIncident.insert();
44+
45+
if (newIncidentSysId) {
46+
// Get the incident number for the successful response.
47+
var incNumber = grIncident.getRecord().getValue('number');
48+
49+
// Log the successful incident creation.
50+
gs.info('Critical P1 incident ' + incNumber + ' created from alert from ' + source);
51+
52+
// Prepare the success response.
53+
var responseBody = {
54+
"status": "success",
55+
"message": "Critical incident created successfully.",
56+
"incident_number": incNumber,
57+
"incident_sys_id": newIncidentSysId
58+
};
59+
response.setStatus(201); // Created
60+
response.setBody(responseBody);
61+
} else {
62+
// Handle database insertion failure.
63+
response.setStatus(500); // Internal Server Error
64+
response.setBody({
65+
"status": "error",
66+
"message": "Failed to create the incident record."
67+
});
68+
}
69+
70+
} catch (ex) {
71+
// Handle any exceptions during processing.
72+
gs.error('An error occurred during critical alert incident creation: ' + ex);
73+
response.setStatus(500);
74+
response.setBody({
75+
"status": "error",
76+
"message": "An internal server error occurred."
77+
});
78+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# GlideRecord Conditional Batch Update
2+
3+
## Description
4+
This snippet updates multiple records in a ServiceNow table based on a GlideRecord encoded query.
5+
It logs all updated records and provides a safe, controlled way to perform batch updates.
6+
7+
## Prerequisites
8+
- Server-side context (Background Script, Script Include, Business Rule)
9+
- Access to the table
10+
- Knowledge of GlideRecord and encoded queries
11+
12+
## Note
13+
- Works in Global Scope
14+
- Server-side execution only
15+
- Logs updated records for verification
16+
- Can be used for maintenance, bulk updates, or automated scripts
17+
18+
## Usage
19+
```javascript
20+
// Update all active low-priority incidents to priority=2 and state=2
21+
batchUpdate('incident', 'active=true^priority=5', {priority: 2, state: 2});
22+
```
23+
24+
## Sample Output
25+
```
26+
Updated record: abc123
27+
Updated record: def456
28+
Updated record: ghi789
29+
Batch update completed. Total records updated: 3
30+
```
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Update multiple records in a table based on an encoded query with field-level updates.
3+
* Logs all updated records for verification.
4+
*
5+
* @param {string} table - Name of the table
6+
* @param {string} encodedQuery - GlideRecord encoded query to select records
7+
* @param {object} fieldUpdates - Key-value pairs of fields to update
8+
*/
9+
function batchUpdate(table, encodedQuery, fieldUpdates) {
10+
if (!table || !encodedQuery || !fieldUpdates || typeof fieldUpdates !== 'object') {
11+
gs.error('Table, encodedQuery, and fieldUpdates (object) are required.');
12+
return;
13+
}
14+
15+
var gr = new GlideRecord(table);
16+
gr.addEncodedQuery(encodedQuery);
17+
gr.query();
18+
19+
var count = 0;
20+
while (gr.next()) {
21+
for (var field in fieldUpdates) {
22+
if (gr.isValidField(field)) {
23+
gr.setValue(field, fieldUpdates[field]);
24+
} else {
25+
gs.warn('Invalid field: ' + field + ' in table ' + table);
26+
}
27+
}
28+
29+
gr.update();
30+
gs.info('Updated record: ' + gr.getValue('sys_id'));
31+
count++;
32+
}
33+
34+
gs.info('Batch update completed. Total records updated: ' + count);
35+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
ServiceNow business rule for server-side field validation based on form views.
2+
3+
This ServiceNow business rule provides comprehensive server-side validation for multiple form fields when users access specific form views. The script ensures data integrity by validating that critical fields contain expected values before allowing record submission, making it perfect for enforcing business rules and data consistency across your ServiceNow instance.
4+
5+
What This Script Does:
6+
7+
The business rule automatically validates multiple fields against predefined expected values when a specific form view is accessed. Key features include:
8+
9+
View-Based Validation: Only triggers when accessing a specified form view
10+
Multiple Field Support: Validates multiple fields simultaneously with customizable criteria
11+
Required Field Checking: Ensures mandatory fields are not empty or null
12+
Value Validation: Confirms fields contain expected values according to business rules
13+
User-Friendly Messaging: Provides clear, consolidated error messages explaining all validation failures
14+
Server-Side Security: Performs validation on the server to prevent client-side bypassing
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
(function executeRule(current, previous /*null when async*/ ) {
2+
var TARGET_VIEW_NAME = 'your_target_view_name';
3+
var FIELD_VALIDATIONS = [
4+
{field: 'field1', expectedValue: 'value1', errorMsg: 'Field 1 validation failed'},
5+
{field: 'field2', expectedValue: 'value2', errorMsg: 'Field 2 validation failed'},
6+
{field: 'field3', expectedValue: 'value3', errorMsg: 'Field 3 validation failed'}
7+
];
8+
9+
var gURI = "";
10+
try {
11+
gURI = gs.action.getGlideURI();
12+
} catch(e) {
13+
return;
14+
}
15+
16+
var view_name = gURI.get('sysparm_view');
17+
18+
if (view_name == TARGET_VIEW_NAME) {
19+
var validationErrors = [];
20+
21+
for (var i = 0; i < FIELD_VALIDATIONS.length; i++) {
22+
var validation = FIELD_VALIDATIONS[i];
23+
var fieldValue = current.getValue(validation.field);
24+
25+
if (gs.nil(fieldValue)) {
26+
validationErrors.push(validation.field + ' is required');
27+
continue;
28+
}
29+
30+
if (fieldValue != validation.expectedValue) {
31+
validationErrors.push(validation.errorMsg);
32+
}
33+
}
34+
35+
if (validationErrors.length > 0) {
36+
gs.addErrorMessage(gs.getMessage("Validation failed: {0}", validationErrors.join(', ')));
37+
}
38+
}
39+
})(current, previous);

0 commit comments

Comments
 (0)