ServiceNow Development Complete Guide — من الصفر إلى الاحتراف
Platform: ServiceNow Australia (Sep 2026) · Scope:
x_786686_hr_leave
دليل شامل للتطوير يغطي كل أنواع السكربتات والـ APIs والأدوات
Table of Contents
- Development Environment Setup
- Server-Side Scripting
- Business Rules
- Script Includes
- Client-Side Scripting
- GlideAjax
- REST APIs
- Flow Designer
- UI Components
- Debugging
1. Development Environment Setup
Direct URLs
| Component | URL |
|---|---|
| Studio | {BASE}/$studio.do |
| Tables | {BASE}/sys_db_object_list.do |
| Scripts | {BASE}/sys_script_list.do |
| Update Sets | {BASE}/sys_update_set_list.do |
Lab 1: Create Application
// Step 1: Open Studio
// All > System Applications > Studio
// Step 2: Create New Application
// Name: HR Leave Demo
// Scope: x_786686_hr_leave
// Step 3: Create Table
var gr = new GlideRecord('sys_db_object');
gr.initialize();
gr.name = 'x_786686_hr_leave_request';
gr.label = 'Leave Request';
gr.super_class = 'task';
gr.insert();
2. Server-Side Scripting
GlideRecord - Complete Reference
// === QUERY ===
var gr = new GlideRecord('incident');
gr.addQuery('state', 1);
gr.addQuery('priority', '<=', 2);
gr.addEncodedQuery('active=true^category=software');
gr.orderByDesc('sys_created_on');
gr.setLimit(100);
gr.query();
while (gr.next()) {
gs.info(gr.number + ': ' + gr.short_description);
}
// === INSERT ===
var newRec = new GlideRecord('incident');
newRec.initialize();
newRec.short_description = 'Test incident';
newRec.caller_id = gs.getUserID();
var sys_id = newRec.insert();
// === UPDATE ===
var upd = new GlideRecord('incident');
if (upd.get('number', 'INC0010001')) {
upd.state = 2;
upd.update();
}
// === DELETE ===
var del = new GlideRecord('incident');
del.addQuery('state', 7);
del.deleteMultiple(); // Careful!
GlideAggregate
var ga = new GlideAggregate('incident');
ga.addAggregate('COUNT');
ga.addAggregate('AVG', 'reassignment_count');
ga.groupBy('priority');
ga.query();
while (ga.next()) {
gs.info('Priority ' + ga.priority + ': ' + ga.getAggregate('COUNT'));
}
GlideDateTime
var now = new GlideDateTime();
var future = new GlideDateTime();
future.addDays(30);
var duration = GlideDateTime.subtract(now, future);
gs.info('Days: ' + duration.getDayPart());
// Business days
var schedule = new GlideSchedule('08fcd0830a0a0b2600079f56b1adb9ae');
var bizDays = schedule.duration(now, future);
3. Business Rules
Direct URL: {BASE}/sys_script_list.do
Types
| Type | When | Use |
|---|---|---|
| before | Before save | Validation, auto-populate |
| after | After save | Notifications, related records |
| async | Background | Heavy processing, integrations |
| display | Form load | Scratchpad data |
Example: Before Insert
// Name: HR Leave - Auto Populate
// Table: x_786686_hr_leave_request
// When: before insert
(function executeRule(current, previous) {
// Get employee's manager
var emp = current.employee.getRefRecord();
if (emp.isValidRecord()) {
current.manager = emp.manager;
current.short_description = current.leave_type.getDisplayValue() +
' - ' + emp.name;
}
// Calculate duration
if (current.start_date && current.end_date) {
var start = new GlideDateTime(current.start_date);
var end = new GlideDateTime(current.end_date);
var dur = GlideDateTime.subtract(start, end);
current.duration_days = dur.getDayPart() + 1;
}
})(current, previous);
Example: Validation
// Name: HR Leave - Validate Dates
// When: before insert, update
(function executeRule(current, previous) {
var start = new GlideDateTime(current.start_date);
var end = new GlideDateTime(current.end_date);
var today = new GlideDateTime();
if (start.before(today)) {
current.setAbortAction(true);
gs.addErrorMessage('Start date cannot be in the past');
return;
}
if (end.before(start)) {
current.setAbortAction(true);
gs.addErrorMessage('End date must be after start date');
return;
}
var days = GlideDateTime.subtract(start, end).getDayPart() + 1;
if (days > 30) {
current.setAbortAction(true);
gs.addErrorMessage('Leave cannot exceed 30 days');
}
})(current, previous);
Example: After Insert - Approval
// Name: HR Leave - Create Approval
// When: after insert
(function executeRule(current, previous) {
var approval = new GlideRecord('sysapproval_approver');
approval.initialize();
approval.sysapproval = current.sys_id;
approval.source_table = 'x_786686_hr_leave_request';
approval.approver = current.manager;
approval.state = 'requested';
approval.insert();
gs.eventQueue('x_786686_hr_leave.request.submitted', current,
current.employee.email, current.manager.email);
})(current, previous);
4. Script Includes
Direct URL: {BASE}/sys_script_include_list.do
Basic Script Include
// Name: LeaveRequestUtil
// API Name: x_786686_hr_leave.LeaveRequestUtil
// Client Callable: false
var LeaveRequestUtil = Class.create();
LeaveRequestUtil.prototype = {
initialize: function() {
this.log = new GSLog('x_786686_hr_leave.log', 'LeaveRequestUtil');
},
createRequest: function(params) {
var gr = new GlideRecord('x_786686_hr_leave_request');
gr.initialize();
gr.employee = params.employee;
gr.leave_type = params.leaveType;
gr.start_date = params.startDate;
gr.end_date = params.endDate;
gr.reason = params.reason || '';
return gr.insert();
},
approveRequest: function(requestId, approverId) {
var gr = new GlideRecord('x_786686_hr_leave_request');
if (!gr.get(requestId)) return false;
gr.state = 3;
gr.approval_status = 'approved';
gr.work_notes = 'Approved by ' + this._getUserName(approverId);
gr.update();
// Deduct balance
new x_786686_hr_leave.LeaveBalanceUtil().deductBalance(
gr.employee.toString(),
gr.leave_type.toString(),
parseFloat(gr.duration_days)
);
return true;
},
getEmployeeRequests: function(employeeId, filters) {
var results = [];
filters = filters || {};
var gr = new GlideRecord('x_786686_hr_leave_request');
gr.addQuery('employee', employeeId);
if (filters.state) gr.addQuery('state', filters.state);
gr.orderByDesc('sys_created_on');
gr.query();
while (gr.next()) {
results.push({
sys_id: gr.getUniqueValue(),
number: gr.number.toString(),
leave_type: gr.leave_type.getDisplayValue(),
start_date: gr.start_date.getDisplayValue(),
duration: parseFloat(gr.duration_days) || 0,
state: gr.state.getDisplayValue()
});
}
return results;
},
_getUserName: function(userId) {
var user = new GlideRecord('sys_user');
return user.get(userId) ? user.name.toString() : 'Unknown';
},
type: 'LeaveRequestUtil'
};
Leave Balance Utility
// Name: LeaveBalanceUtil
// API Name: x_786686_hr_leave.LeaveBalanceUtil
var LeaveBalanceUtil = Class.create();
LeaveBalanceUtil.prototype = {
initialize: function() {},
getBalance: function(employeeId, leaveType) {
leaveType = leaveType || 'annual';
var year = new GlideDateTime().getYearLocalTime();
var alloc = new GlideRecord('x_786686_hr_leave_allocation');
alloc.addQuery('employee', employeeId);
alloc.addQuery('leave_type', leaveType);
alloc.addQuery('year', year);
alloc.query();
if (alloc.next()) {
var allocated = parseInt(alloc.days_allocated) || 0;
var used = parseInt(alloc.days_used) || 0;
return allocated - used;
}
return 0;
},
getAllBalances: function(employeeId) {
var balances = {};
var types = ['annual', 'sick', 'emergency', 'unpaid'];
for (var i = 0; i < types.length; i++) {
balances[types[i]] = this.getBalance(employeeId, types[i]);
}
return balances;
},
deductBalance: function(employeeId, leaveType, days) {
var year = new GlideDateTime().getYearLocalTime();
var alloc = new GlideRecord('x_786686_hr_leave_allocation');
alloc.addQuery('employee', employeeId);
alloc.addQuery('leave_type', leaveType);
alloc.addQuery('year', year);
alloc.query();
if (alloc.next()) {
alloc.days_used = (parseInt(alloc.days_used) || 0) + days;
alloc.update();
return true;
}
return false;
},
type: 'LeaveBalanceUtil'
};
5. Client-Side Scripting
Direct URL: {BASE}/sys_script_client_list.do
g_form API Reference
// Get/Set values
var value = g_form.getValue('field_name');
var display = g_form.getDisplayValue('reference_field');
g_form.setValue('field_name', 'new_value');
// Visibility
g_form.setVisible('field_name', true);
g_form.setDisplay('field_name', false);
// Mandatory
g_form.setMandatory('field_name', true);
// Read-only
g_form.setReadOnly('field_name', true);
// Messages
g_form.addInfoMessage('Success!');
g_form.addErrorMessage('Error!');
g_form.showFieldMsg('field', 'Invalid', 'error');
g_form.hideFieldMsg('field');
// Options
g_form.addOption('choice_field', 'value', 'Label');
g_form.removeOption('choice_field', 'value');
g_form.clearOptions('choice_field');
// Reference
g_form.getReference('caller_id', function(ref) {
alert('Name: ' + ref.name);
});
OnLoad Script
// Name: Initialize Leave Form
// Type: onLoad
function onLoad() {
if (g_form.isNewRecord()) {
g_form.setValue('employee', g_user.userID);
if (g_scratchpad.leave_balances) {
var balances = JSON.parse(g_scratchpad.leave_balances);
g_form.addInfoMessage('Annual: ' + balances.annual +
' | Sick: ' + balances.sick + ' days');
}
}
}
OnChange Script
// Name: Calculate Duration
// Type: onChange
// Field: start_date, end_date
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading) return;
var start = g_form.getValue('start_date');
var end = g_form.getValue('end_date');
if (!start || !end) return;
var startDate = new Date(start);
var endDate = new Date(end);
if (endDate < startDate) {
g_form.showFieldMsg('end_date', 'End must be after start', 'error');
return;
}
var days = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24)) + 1;
g_form.setValue('duration_days', days);
g_form.hideFieldMsg('end_date');
checkBalance(days);
}
function checkBalance(days) {
var ga = new GlideAjax('x_786686_hr_leave.LeaveAjaxProcessor');
ga.addParam('sysparm_name', 'getBalance');
ga.addParam('sysparm_employee_id', g_form.getValue('employee'));
ga.addParam('sysparm_leave_type', g_form.getValue('leave_type'));
ga.getXMLAnswer(function(answer) {
var result = JSON.parse(answer);
if (days > result.balance) {
g_form.showFieldMsg('duration_days',
'Exceeds balance (' + result.balance + ')', 'error');
}
});
}
OnSubmit Script
// Name: Validate Before Submit
// Type: onSubmit
function onSubmit() {
var duration = parseFloat(g_form.getValue('duration_days')) || 0;
if (duration <= 0) {
g_form.addErrorMessage('Invalid duration');
return false;
}
if (duration > 30) {
g_form.addErrorMessage('Cannot exceed 30 days');
return false;
}
return confirm('Submit this leave request?');
}
6. GlideAjax
Client Script
function getEmployeeData(employeeId) {
var ga = new GlideAjax('x_786686_hr_leave.LeaveAjaxProcessor');
ga.addParam('sysparm_name', 'getEmployeeData');
ga.addParam('sysparm_employee_id', employeeId);
ga.getXMLAnswer(function(answer) {
try {
var result = JSON.parse(answer);
if (result.success) {
g_form.setValue('manager', result.manager_id);
g_form.addInfoMessage('Balance: ' + result.balance);
} else {
g_form.addErrorMessage(result.error);
}
} catch (e) {
g_form.addErrorMessage('Error: ' + e.message);
}
});
}
Server Script Include
// Name: LeaveAjaxProcessor
// Client Callable: true
var LeaveAjaxProcessor = Class.create();
LeaveAjaxProcessor.prototype = Object.extendsObject(AbstractAjaxProcessor, {
getBalance: function() {
var employeeId = this.getParameter('sysparm_employee_id');
var leaveType = this.getParameter('sysparm_leave_type') || 'annual';
var util = new x_786686_hr_leave.LeaveBalanceUtil();
var balance = util.getBalance(employeeId, leaveType);
return JSON.stringify({
success: true,
balance: balance
});
},
getEmployeeData: function() {
var employeeId = this.getParameter('sysparm_employee_id');
var emp = new GlideRecord('sys_user');
if (!emp.get(employeeId)) {
return JSON.stringify({ error: 'Employee not found' });
}
var util = new x_786686_hr_leave.LeaveBalanceUtil();
return JSON.stringify({
success: true,
name: emp.name.toString(),
email: emp.email.toString(),
manager_id: emp.manager.toString(),
manager_name: emp.manager.getDisplayValue(),
balance: util.getBalance(employeeId, 'annual')
});
},
checkAvailability: function() {
var employeeId = this.getParameter('sysparm_employee_id');
var startDate = this.getParameter('sysparm_start_date');
var endDate = this.getParameter('sysparm_end_date');
var gr = new GlideRecord('x_786686_hr_leave_request');
gr.addQuery('employee', employeeId);
gr.addQuery('state', 'NOT IN', '6,7');
var qc = gr.addQuery('start_date', '<=', endDate);
qc.addCondition('end_date', '>=', startDate);
gr.query();
var conflicts = [];
while (gr.next()) {
conflicts.push({
number: gr.number.toString(),
dates: gr.start_date.getDisplayValue() + ' - ' +
gr.end_date.getDisplayValue()
});
}
return JSON.stringify({
available: conflicts.length === 0,
conflicts: conflicts
});
},
type: 'LeaveAjaxProcessor'
});
7. REST APIs
Direct URL: {BASE}/sys_ws_definition_list.do
Scripted REST API
// API: HR Leave API
// Path: /api/x_786686_hr_leave/hr_leave
// === GET /requests ===
(function process(request, response) {
var employeeId = request.queryParams.employee_id;
var state = request.queryParams.state;
var gr = new GlideRecord('x_786686_hr_leave_request');
if (employeeId) gr.addQuery('employee', employeeId);
if (state) gr.addQuery('state', state);
gr.orderByDesc('sys_created_on');
gr.setLimit(100);
gr.query();
var results = [];
while (gr.next()) {
results.push({
sys_id: gr.getUniqueValue(),
number: gr.number.toString(),
employee: gr.employee.getDisplayValue(),
leave_type: gr.leave_type.getDisplayValue(),
start_date: gr.start_date.getValue(),
end_date: gr.end_date.getValue(),
duration: parseFloat(gr.duration_days),
state: gr.state.getDisplayValue()
});
}
return { count: results.length, requests: results };
})(request, response);
// === POST /requests ===
(function process(request, response) {
var body = request.body.data;
// Validate
if (!body.employee_id || !body.leave_type ||
!body.start_date || !body.end_date) {
response.setStatus(400);
return { error: 'Missing required fields' };
}
var gr = new GlideRecord('x_786686_hr_leave_request');
gr.initialize();
gr.employee = body.employee_id;
gr.leave_type = body.leave_type;
gr.start_date = body.start_date;
gr.end_date = body.end_date;
gr.reason = body.reason || '';
var sys_id = gr.insert();
if (sys_id) {
response.setStatus(201);
gr.get(sys_id);
return {
success: true,
sys_id: sys_id,
number: gr.number.toString()
};
}
response.setStatus(500);
return { error: 'Failed to create' };
})(request, response);
Outbound REST
var ExternalHRIntegration = Class.create();
ExternalHRIntegration.prototype = {
initialize: function() {
this.endpoint = gs.getProperty('x_786686_hr_leave.external_endpoint');
this.apiKey = gs.getProperty('x_786686_hr_leave.api_key');
},
syncRequest: function(requestSysId) {
var gr = new GlideRecord('x_786686_hr_leave_request');
if (!gr.get(requestSysId)) return null;
try {
var rest = new sn_ws.RESTMessageV2();
rest.setEndpoint(this.endpoint + '/leave-requests');
rest.setHttpMethod('POST');
rest.setRequestHeader('Authorization', 'Bearer ' + this.apiKey);
rest.setRequestHeader('Content-Type', 'application/json');
rest.setRequestBody(JSON.stringify({
external_id: gr.number.toString(),
employee: gr.employee.employee_number.toString(),
type: gr.leave_type.toString(),
start: gr.start_date.getValue(),
end: gr.end_date.getValue(),
days: parseFloat(gr.duration_days)
}));
var resp = rest.execute();
if (resp.getStatusCode() == 201) {
return JSON.parse(resp.getBody());
}
} catch (e) {
gs.error('Sync error: ' + e.message);
}
return null;
},
type: 'ExternalHRIntegration'
};
8. Flow Designer
Direct URL: {BASE}/now/flow-designer
Leave Approval Flow
Name: HR Leave Request Approval
Trigger: Record Updated
Table: x_786686_hr_leave_request
Condition: State changes to Submitted
Steps:
1. Look Up Record
- Table: sys_user
- ID: Trigger → Employee
2. If duration > 5 days
- Ask Approval: Manager (2 days)
- If Approved: Ask Approval: HR Admin
3. If All Approved:
- Update Record: State = Approved
- Send Email: Employee
- Run Script: Deduct balance
4. If Rejected:
- Update Record: State = Rejected
- Send Email: Employee with reason
Custom Action
// Action: Calculate Business Days
(function execute(inputs, outputs) {
var start = new GlideDateTime(inputs.start_date);
var end = new GlideDateTime(inputs.end_date);
var schedule = new GlideSchedule(inputs.schedule_id ||
'08fcd0830a0a0b2600079f56b1adb9ae');
var duration = schedule.duration(start, end);
outputs.business_days = Math.ceil(
duration.getNumericValue() / (8 * 60 * 60 * 1000)
);
})(inputs, outputs);
9. UI Components
UI Action - Form Button
// Name: Submit for Approval
// Table: x_786686_hr_leave_request
// Form button: true
// Condition: current.state == 1
// Client Script
function submitForApproval() {
if (!g_form.getValue('employee')) {
alert('Select employee');
return false;
}
if (confirm('Submit for approval?')) {
g_form.setValue('state', '2');
gsftSubmit(null, g_form.getFormElement(), 'submit');
}
}
// Server Script
if (typeof window == 'undefined') {
current.state = 2;
current.update();
gs.eventQueue('x_786686_hr_leave.submitted', current);
action.setRedirectURL(current);
}
UI Policy
Name: Lock Fields After Submit
Table: x_786686_hr_leave_request
Condition: state != 1
Reverse: true
Actions:
- employee: Read only
- leave_type: Read only
- start_date: Read only
- end_date: Read only
10. Debugging
Logging
// GSLog (recommended)
var log = new GSLog('x_786686_hr_leave.log', 'MyScript');
log.debug('Debug info');
log.info('Info message');
log.warn('Warning');
log.error('Error');
// Simple logging
gs.info('Info message');
gs.error('Error message');
// Session debug
gs.setSessionDebug(true);
// ... operations ...
gs.setSessionDebug(false);
Performance
// Timer
var timer = new GlideStopWatch();
// ... operation ...
gs.info('Took: ' + timer.getTime() + 'ms');
// Query optimization
gr.setFields('sys_id,number,short_description');
gr.setLimit(100);
Direct URLs Quick Reference
| Page | URL |
|---|---|
| Studio | {BASE}/$studio.do |
| Business Rules | {BASE}/sys_script_list.do |
| Client Scripts | {BASE}/sys_script_client_list.do |
| Script Includes | {BASE}/sys_script_include_list.do |
| UI Actions | {BASE}/sys_ui_action_list.do |
| UI Policies | {BASE}/sys_ui_policy_list.do |
| Flow Designer | {BASE}/now/flow-designer |
| REST API Explorer | {BASE}/$restapi.do |
| Background Scripts | {BASE}/sys.scripts.do |
| System Logs | {BASE}/syslog_list.do |
Next: Administration Guide | Portals Guide