Skip to content

Commit 189a94d

Browse files
Form Field Masking
1 parent 8ecda26 commit 189a94d

File tree

2 files changed

+340
-0
lines changed

2 files changed

+340
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Form Field Masking
2+
3+
A powerful form field masking solution for ServiceNow Catalog Items that provides dynamic input formatting and sensitive data protection.
4+
5+
## Features
6+
7+
- Automatic input formatting
8+
- Predefined mask patterns
9+
- Sensitive data protection
10+
- Dynamic mask detection
11+
- Visual indicators for masked fields
12+
- Toggle visibility for sensitive data
13+
- Placeholder support
14+
- Real-time mask application
15+
16+
## Supported Mask Types
17+
18+
1. Phone Numbers: `(###) ###-####`
19+
2. Social Security Numbers: `###-##-####`
20+
3. Credit Cards: `#### #### #### ####`
21+
4. Dates: `##/##/####`
22+
5. Currency: `$ ###,###.##`
23+
6. IP Addresses: `###.###.###.###`
24+
25+
## Implementation
26+
27+
### Basic Setup
28+
29+
1. Create a new Catalog Client Script:
30+
```javascript
31+
Table: Catalog Client Script [catalog_script_client]
32+
Type: Both onLoad and onChange
33+
Active: true
34+
```
35+
36+
2. Copy the content of `script.js` into your script
37+
38+
### Configuring Field Masks
39+
40+
Two ways to configure masks:
41+
42+
1. **Automatic Detection**:
43+
- Fields are automatically masked based on their names
44+
- Example: fields containing "phone" will use phone number mask
45+
46+
2. **Manual Configuration**:
47+
```javascript
48+
// Add a variable to your catalog item
49+
Name: fieldname_mask_type
50+
Type: String
51+
Value: phone|ssn|creditCard|date|currency|ipAddress
52+
```
53+
54+
## Usage Examples
55+
56+
### Basic Field Masking
57+
```javascript
58+
// Phone number field will automatically format: (555) 123-4567
59+
var masker = new FormFieldMasker();
60+
masker.applyMask('phone_number', '5551234567');
61+
```
62+
63+
### Sensitive Data Masking
64+
```javascript
65+
// SSN will be masked and get toggle visibility control
66+
var masker = new FormFieldMasker();
67+
masker.applyMask('ssn', '123456789');
68+
```
69+
70+
## Customization
71+
72+
### Adding Custom Masks
73+
74+
```javascript
75+
var masker = new FormFieldMasker();
76+
masker.masks.customFormat = {
77+
pattern: '@@###',
78+
placeholder: '_',
79+
allowedChars: /[A-Za-z0-9]/
80+
};
81+
```
82+
83+
### Styling
84+
85+
Add these CSS classes to your style sheet:
86+
```css
87+
.sensitive-field {
88+
background-color: #f8f8f8;
89+
}
90+
91+
.mask-sensitive {
92+
-webkit-text-security: disc;
93+
}
94+
95+
.toggle-sensitive {
96+
cursor: pointer;
97+
margin-left: 5px;
98+
}
99+
```
100+
101+
## Technical Details
102+
103+
### Dependencies
104+
- ServiceNow Platform UI Framework
105+
- GlideForm API
106+
- Prototype.js
107+
108+
### Browser Support
109+
- Works with all modern browsers
110+
- ES5+ compatible
111+
112+
## Best Practices
113+
114+
1. Performance
115+
- Efficient regex patterns
116+
- Optimized mask application
117+
- Minimal DOM manipulation
118+
119+
2. User Experience
120+
- Clear visual feedback
121+
- Intuitive mask patterns
122+
- Easy visibility toggling
123+
124+
3. Security
125+
- Proper handling of sensitive data
126+
- Client-side only masking
127+
- Clear security indicators
128+
129+
## Troubleshooting
130+
131+
Common issues and solutions:
132+
133+
1. Mask not applying
134+
- Verify field name matches mask type
135+
- Check console for errors
136+
- Confirm mask pattern is valid
137+
138+
2. Sensitive data handling
139+
- Ensure proper CSS classes
140+
- Verify toggle functionality
141+
- Check browser compatibility
142+
143+
## Version Information
144+
145+
- Compatible with ServiceNow: Rome and later
146+
- Last Updated: October 2025
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/**
2+
* Form Field Masking
3+
* Provides dynamic input masking for form fields with various formats
4+
*/
5+
6+
function onLoad() {
7+
var masker = new FormFieldMasker();
8+
masker.initializeMasks();
9+
}
10+
11+
function onChange(control, oldValue, newValue, isLoading) {
12+
if (isLoading) return;
13+
14+
var masker = new FormFieldMasker();
15+
masker.applyMask(control, newValue);
16+
}
17+
18+
var FormFieldMasker = Class.create();
19+
FormFieldMasker.prototype = {
20+
initialize: function() {
21+
// Predefined mask patterns
22+
this.masks = {
23+
phone: {
24+
pattern: '(###) ###-####',
25+
placeholder: '_',
26+
allowedChars: /[0-9]/
27+
},
28+
ssn: {
29+
pattern: '###-##-####',
30+
placeholder: 'X',
31+
allowedChars: /[0-9]/,
32+
sensitive: true
33+
},
34+
creditCard: {
35+
pattern: '#### #### #### ####',
36+
placeholder: '_',
37+
allowedChars: /[0-9]/,
38+
sensitive: true
39+
},
40+
date: {
41+
pattern: '##/##/####',
42+
placeholder: '_',
43+
allowedChars: /[0-9]/
44+
},
45+
currency: {
46+
pattern: '$ ###,###.##',
47+
placeholder: '0',
48+
allowedChars: /[0-9.]/
49+
},
50+
ipAddress: {
51+
pattern: '###.###.###.###',
52+
placeholder: '_',
53+
allowedChars: /[0-9]/
54+
}
55+
};
56+
57+
// Field to mask mapping
58+
this.fieldMasks = {};
59+
},
60+
61+
initializeMasks: function() {
62+
var fields = g_form.getFields();
63+
fields.forEach(function(field) {
64+
var maskType = this._getMaskType(field.getName());
65+
if (maskType) {
66+
this.fieldMasks[field.getName()] = maskType;
67+
this._setupField(field.getName(), maskType);
68+
}
69+
}, this);
70+
},
71+
72+
applyMask: function(fieldName, value) {
73+
var maskType = this.fieldMasks[fieldName] || this._getMaskType(fieldName);
74+
if (!maskType || !value) return;
75+
76+
var mask = this.masks[maskType];
77+
if (!mask) return;
78+
79+
// Clean the input value
80+
var cleanValue = this._cleanValue(value, mask.allowedChars);
81+
82+
// Apply the mask
83+
var maskedValue = this._applyMaskPattern(cleanValue, mask);
84+
85+
// Update the field value
86+
if (maskedValue !== value) {
87+
g_form.setValue(fieldName, maskedValue, maskedValue);
88+
}
89+
90+
// Apply visual treatment for sensitive fields
91+
if (mask.sensitive) {
92+
this._applySensitiveFieldTreatment(fieldName);
93+
}
94+
},
95+
96+
_getMaskType: function(fieldName) {
97+
// Check for mask type from field attributes
98+
var maskType = g_form.getValue(fieldName + '_mask_type');
99+
if (this.masks[maskType]) {
100+
return maskType;
101+
}
102+
103+
// Auto-detect based on field name
104+
var fieldLower = fieldName.toLowerCase();
105+
if (fieldLower.includes('phone')) return 'phone';
106+
if (fieldLower.includes('ssn')) return 'ssn';
107+
if (fieldLower.includes('credit') || fieldLower.includes('card')) return 'creditCard';
108+
if (fieldLower.includes('date')) return 'date';
109+
if (fieldLower.includes('currency') || fieldLower.includes('price')) return 'currency';
110+
if (fieldLower.includes('ip')) return 'ipAddress';
111+
112+
return null;
113+
},
114+
115+
_setupField: function(fieldName, maskType) {
116+
var mask = this.masks[maskType];
117+
if (!mask) return;
118+
119+
// Set placeholder text
120+
g_form.setPlaceholder(fieldName, mask.pattern.replace(/#/g, mask.placeholder));
121+
122+
// Add visual indicator for sensitive fields
123+
if (mask.sensitive) {
124+
this._applySensitiveFieldTreatment(fieldName);
125+
}
126+
127+
// Apply initial mask if value exists
128+
var currentValue = g_form.getValue(fieldName);
129+
if (currentValue) {
130+
this.applyMask(fieldName, currentValue);
131+
}
132+
},
133+
134+
_cleanValue: function(value, allowedChars) {
135+
return value.split('').filter(function(char) {
136+
return allowedChars.test(char);
137+
}).join('');
138+
},
139+
140+
_applyMaskPattern: function(value, mask) {
141+
var result = mask.pattern;
142+
var valueIndex = 0;
143+
144+
// Replace # in pattern with actual values
145+
for (var i = 0; i < result.length && valueIndex < value.length; i++) {
146+
if (result[i] === '#') {
147+
result = result.substr(0, i) + value[valueIndex++] + result.substr(i + 1);
148+
}
149+
}
150+
151+
// Replace remaining # with placeholder
152+
result = result.replace(/#/g, mask.placeholder);
153+
154+
return result;
155+
},
156+
157+
_applySensitiveFieldTreatment: function(fieldName) {
158+
// Add sensitive field styling
159+
var element = g_form.getElement(fieldName);
160+
if (element) {
161+
element.addClassName('sensitive-field');
162+
163+
// Add eye icon to toggle visibility
164+
var container = element.up();
165+
if (!container.down('.toggle-sensitive')) {
166+
var toggle = new Element('span', {
167+
'class': 'toggle-sensitive icon-eye',
168+
'title': 'Toggle field visibility'
169+
});
170+
toggle.observe('click', function() {
171+
this._toggleSensitiveField(fieldName);
172+
}.bind(this));
173+
container.insert(toggle);
174+
}
175+
}
176+
},
177+
178+
_toggleSensitiveField: function(fieldName) {
179+
var element = g_form.getElement(fieldName);
180+
if (element) {
181+
var isHidden = element.hasClassName('mask-sensitive');
182+
element.toggleClassName('mask-sensitive');
183+
184+
var toggle = element.up().down('.toggle-sensitive');
185+
if (toggle) {
186+
toggle.title = isHidden ? 'Hide sensitive data' : 'Show sensitive data';
187+
toggle.toggleClassName('icon-eye');
188+
toggle.toggleClassName('icon-eye-slash');
189+
}
190+
}
191+
},
192+
193+
type: 'FormFieldMasker'
194+
};

0 commit comments

Comments
 (0)