Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Form Field Masking

A powerful form field masking solution for ServiceNow Catalog Items that provides dynamic input formatting and sensitive data protection.

## Features

- Automatic input formatting
- Predefined mask patterns
- Sensitive data protection
- Dynamic mask detection
- Visual indicators for masked fields
- Toggle visibility for sensitive data
- Placeholder support
- Real-time mask application

## Supported Mask Types

1. Phone Numbers: `(###) ###-####`
2. Social Security Numbers: `###-##-####`
3. Credit Cards: `#### #### #### ####`
4. Dates: `##/##/####`
5. Currency: `$ ###,###.##`
6. IP Addresses: `###.###.###.###`

## Implementation

### Basic Setup

1. Create a new Catalog Client Script:
```javascript
Table: Catalog Client Script [catalog_script_client]
Type: Both onLoad and onChange
Active: true
```

2. Copy the content of `script.js` into your script

### Configuring Field Masks

Two ways to configure masks:

1. **Automatic Detection**:
- Fields are automatically masked based on their names
- Example: fields containing "phone" will use phone number mask

2. **Manual Configuration**:
```javascript
// Add a variable to your catalog item
Name: fieldname_mask_type
Type: String
Value: phone|ssn|creditCard|date|currency|ipAddress
```

## Usage Examples

### Basic Field Masking
```javascript
// Phone number field will automatically format: (555) 123-4567
var masker = new FormFieldMasker();
masker.applyMask('phone_number', '5551234567');
```

### Sensitive Data Masking
```javascript
// SSN will be masked and get toggle visibility control
var masker = new FormFieldMasker();
masker.applyMask('ssn', '123456789');
```

## Customization

### Adding Custom Masks

```javascript
var masker = new FormFieldMasker();
masker.masks.customFormat = {
pattern: '@@###',
placeholder: '_',
allowedChars: /[A-Za-z0-9]/
};
```

### Styling

Add these CSS classes to your style sheet:
```css
.sensitive-field {
background-color: #f8f8f8;
}

.mask-sensitive {
-webkit-text-security: disc;
}

.toggle-sensitive {
cursor: pointer;
margin-left: 5px;
}
```

## Technical Details

### Dependencies
- ServiceNow Platform UI Framework
- GlideForm API
- Prototype.js

### Browser Support
- Works with all modern browsers
- ES5+ compatible

## Best Practices

1. Performance
- Efficient regex patterns
- Optimized mask application
- Minimal DOM manipulation

2. User Experience
- Clear visual feedback
- Intuitive mask patterns
- Easy visibility toggling

3. Security
- Proper handling of sensitive data
- Client-side only masking
- Clear security indicators

## Troubleshooting

Common issues and solutions:

1. Mask not applying
- Verify field name matches mask type
- Check console for errors
- Confirm mask pattern is valid

2. Sensitive data handling
- Ensure proper CSS classes
- Verify toggle functionality
- Check browser compatibility

## Version Information

- Compatible with ServiceNow: Rome and later
- Last Updated: October 2025
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/**
* Form Field Masking
* Provides dynamic input masking for form fields with various formats
*/

function onLoad() {
var masker = new FormFieldMasker();
masker.initializeMasks();
}

function onChange(control, oldValue, newValue, isLoading) {
if (isLoading) return;

var masker = new FormFieldMasker();
masker.applyMask(control, newValue);
}

var FormFieldMasker = Class.create();
FormFieldMasker.prototype = {
initialize: function() {
// Predefined mask patterns
this.masks = {
phone: {
pattern: '(###) ###-####',
placeholder: '_',
allowedChars: /[0-9]/
},
ssn: {
pattern: '###-##-####',
placeholder: 'X',
allowedChars: /[0-9]/,
sensitive: true
},
creditCard: {
pattern: '#### #### #### ####',
placeholder: '_',
allowedChars: /[0-9]/,
sensitive: true
},
date: {
pattern: '##/##/####',
placeholder: '_',
allowedChars: /[0-9]/
},
currency: {
pattern: '$ ###,###.##',
placeholder: '0',
allowedChars: /[0-9.]/
},
ipAddress: {
pattern: '###.###.###.###',
placeholder: '_',
allowedChars: /[0-9]/
}
};

// Field to mask mapping
this.fieldMasks = {};
},

initializeMasks: function() {
var fields = g_form.getFields();
fields.forEach(function(field) {
var maskType = this._getMaskType(field.getName());
if (maskType) {
this.fieldMasks[field.getName()] = maskType;
this._setupField(field.getName(), maskType);
}
}, this);
},

applyMask: function(fieldName, value) {
var maskType = this.fieldMasks[fieldName] || this._getMaskType(fieldName);
if (!maskType || !value) return;

var mask = this.masks[maskType];
if (!mask) return;

// Clean the input value
var cleanValue = this._cleanValue(value, mask.allowedChars);

// Apply the mask
var maskedValue = this._applyMaskPattern(cleanValue, mask);

// Update the field value
if (maskedValue !== value) {
g_form.setValue(fieldName, maskedValue, maskedValue);
}

// Apply visual treatment for sensitive fields
if (mask.sensitive) {
this._applySensitiveFieldTreatment(fieldName);
}
},

_getMaskType: function(fieldName) {
// Check for mask type from field attributes
var maskType = g_form.getValue(fieldName + '_mask_type');
if (this.masks[maskType]) {
return maskType;
}

// Auto-detect based on field name
var fieldLower = fieldName.toLowerCase();
if (fieldLower.includes('phone')) return 'phone';
if (fieldLower.includes('ssn')) return 'ssn';
if (fieldLower.includes('credit') || fieldLower.includes('card')) return 'creditCard';
if (fieldLower.includes('date')) return 'date';
if (fieldLower.includes('currency') || fieldLower.includes('price')) return 'currency';
if (fieldLower.includes('ip')) return 'ipAddress';

return null;
},

_setupField: function(fieldName, maskType) {
var mask = this.masks[maskType];
if (!mask) return;

// Set placeholder text
g_form.setPlaceholder(fieldName, mask.pattern.replace(/#/g, mask.placeholder));

// Add visual indicator for sensitive fields
if (mask.sensitive) {
this._applySensitiveFieldTreatment(fieldName);
}

// Apply initial mask if value exists
var currentValue = g_form.getValue(fieldName);
if (currentValue) {
this.applyMask(fieldName, currentValue);
}
},

_cleanValue: function(value, allowedChars) {
return value.split('').filter(function(char) {
return allowedChars.test(char);
}).join('');
},

_applyMaskPattern: function(value, mask) {
var result = mask.pattern;
var valueIndex = 0;

// Replace # in pattern with actual values
for (var i = 0; i < result.length && valueIndex < value.length; i++) {
if (result[i] === '#') {
result = result.substr(0, i) + value[valueIndex++] + result.substr(i + 1);
}
}

// Replace remaining # with placeholder
result = result.replace(/#/g, mask.placeholder);

return result;
},

_applySensitiveFieldTreatment: function(fieldName) {
// Add sensitive field styling
var element = g_form.getElement(fieldName);
if (element) {
element.addClassName('sensitive-field');

// Add eye icon to toggle visibility
var container = element.up();
if (!container.down('.toggle-sensitive')) {
var toggle = new Element('span', {
'class': 'toggle-sensitive icon-eye',
'title': 'Toggle field visibility'
});
toggle.observe('click', function() {
this._toggleSensitiveField(fieldName);
}.bind(this));
container.insert(toggle);
}
}
},

_toggleSensitiveField: function(fieldName) {
var element = g_form.getElement(fieldName);
if (element) {
var isHidden = element.hasClassName('mask-sensitive');
element.toggleClassName('mask-sensitive');

var toggle = element.up().down('.toggle-sensitive');
if (toggle) {
toggle.title = isHidden ? 'Hide sensitive data' : 'Show sensitive data';
toggle.toggleClassName('icon-eye');
toggle.toggleClassName('icon-eye-slash');
}
}
},

type: 'FormFieldMasker'
};
Loading