From 189a94daca09e6222e8c997e44611a9a505b3532 Mon Sep 17 00:00:00 2001 From: Poojitha Valli Pogaku Date: Sun, 19 Oct 2025 18:54:08 +0530 Subject: [PATCH] Form Field Masking --- .../Form Field Masking/README.md | 146 +++++++++++++ .../Form Field Masking/script.js | 194 ++++++++++++++++++ 2 files changed, 340 insertions(+) create mode 100644 Client-Side Components/Catalog Client Script/Form Field Masking/README.md create mode 100644 Client-Side Components/Catalog Client Script/Form Field Masking/script.js diff --git a/Client-Side Components/Catalog Client Script/Form Field Masking/README.md b/Client-Side Components/Catalog Client Script/Form Field Masking/README.md new file mode 100644 index 0000000000..d13d8985bb --- /dev/null +++ b/Client-Side Components/Catalog Client Script/Form Field Masking/README.md @@ -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 \ No newline at end of file diff --git a/Client-Side Components/Catalog Client Script/Form Field Masking/script.js b/Client-Side Components/Catalog Client Script/Form Field Masking/script.js new file mode 100644 index 0000000000..45030bca1d --- /dev/null +++ b/Client-Side Components/Catalog Client Script/Form Field Masking/script.js @@ -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' +}; \ No newline at end of file