PageSourceSearch

https://eta-canada.info/js/common-form-validation.js

js eta-canada.info collected 2026-09-26 05:55:27 UTC 6,768 bytes, 254 lines download raw bytes

1(function (global) {
2  'use strict';
3
4  var RULES = {
5    name: { min: 2, max: 70 },
6    subject: { min: 10, max: 100 },
7    message: { min: 20, max: 1500 }
8  };
9
10  var FIELD_LABELS = {
11    name: 'Name',
12    subject: 'Subject',
13    message: 'Message'
14  };
15
16  var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
17
18  function getRule(fieldName, overrides) {
19    var baseRule = RULES[fieldName] || {};
20    var overrideRule = overrides && overrides[fieldName] ? overrides[fieldName] : {};
21
22    return {
23      min: typeof overrideRule.min === 'number' ? overrideRule.min : baseRule.min,
24      max: typeof overrideRule.max === 'number' ? overrideRule.max : baseRule.max
25    };
26  }
27
28  function toText(value) {
29    if (value === null || value === undefined) {
30      return '';
31    }
32    return String(value).trim();
33  }
34
35  function inRange(value, min, max) {
36    var len = toText(value).length;
37    return len >= min && len <= max;
38  }
39
40  function validateName(value, ruleOverrides) {
41    var text = toText(value);
42    var rule = getRule('name', ruleOverrides);
43
44    if (!inRange(text, rule.min, rule.max)) {
45      return 'Name: ' + rule.min + ' and ' + rule.max + ' characters.';
46    }
47    return '';
48  }
49
50  function validateEmail(value) {
51    var text = toText(value);
52    if (!EMAIL_REGEX.test(text)) {
53      return 'Please enter a valid email address.';
54    }
55    return '';
56  }
57
58  function validateSubject(value, required, ruleOverrides) {
59    var text = toText(value);
60    var rule = getRule('subject', ruleOverrides);
61
62    if (!required && text.length === 0) {
63      return '';
64    }
65    if (!inRange(text, rule.min, rule.max)) {
66      return 'Subject: ' + rule.min + ' and ' + rule.max + ' characters.';
67    }
68    return '';
69  }
70
71  function validateMessage(value, ruleOverrides) {
72    var text = toText(value);
73    var rule = getRule('message', ruleOverrides);
74
75    if (!inRange(text, rule.min, rule.max)) {
76      return 'Message: ' + rule.min + ' and ' + rule.max + ' characters.';
77    }
78    return '';
79  }
80
81  function getLimitMessage(fieldName, value, ruleOverrides) {
82    var fieldRule = getRule(fieldName, ruleOverrides);
83    var fieldLabel = FIELD_LABELS[fieldName];
84    var text = toText(value);
85
86    if (!fieldRule || !fieldRule.max || !fieldLabel) {
87      return '';
88    }
89
90    if (text.length >= fieldRule.max) {
91      return fieldLabel + ' character limit reached (' + fieldRule.max + ').';
92    }
93
94    return '';
95  }
96
97  function validateForm(values, options) {
98    var opts = options || {};
99    var subjectRequired = !!opts.subjectRequired;
100    var ruleOverrides = opts.ruleOverrides || {};
101
102    var errors = {
103      name: validateName(values && values.name, ruleOverrides),
104      email: validateEmail(values && values.email),
105      subject: validateSubject(values && values.subject, subjectRequired, ruleOverrides),
106      message: validateMessage(values && values.message, ruleOverrides)
107    };
108
109    var isValid = !errors.name && !errors.email && !errors.subject && !errors.message;
110
111    return {
112      isValid: isValid,
113      errors: errors,
114      rules: {
115        name: getRule('name', ruleOverrides),
116        subject: getRule('subject', ruleOverrides),
117        message: getRule('message', ruleOverrides)
118      }
119    };
120  }
121
122  function getElement(selectorOrElement) {
123    if (!selectorOrElement) {
124      return null;
125    }
126
127    if (typeof selectorOrElement === 'string') {
128      return document.querySelector(selectorOrElement);
129    }
130
131    return selectorOrElement;
132  }
133
134  function setFieldError(fieldSelector, errorSelector, message) {
135    var field = getElement(fieldSelector);
136    var error = getElement(errorSelector);
137    var hasMessage = !!toText(message);
138
139    if (field) {
140      if (hasMessage) {
141        field.classList.add('field-error');
142      } else {
143        field.classList.remove('field-error');
144      }
145    }
146
147    if (error) {
148      error.textContent = message || '';
149      error.style.display = hasMessage ? 'block' : 'none';
150    }
151  }
152
153  function clearFieldError(fieldSelector, errorSelector) {
154    var field = getElement(fieldSelector);
155    var error = getElement(errorSelector);
156
157    if (field) {
158      field.classList.remove('field-error');
159    }
160
161    if (error) {
162      error.textContent = '';
163      error.style.display = 'none';
164    }
165  }
166
167  function clearErrors(fieldMap) {
168    Object.keys(fieldMap || {}
168).forEach(function (key) {
169      clearFieldError(fieldMap[key].field, fieldMap[key].error);
170    });
171  }
172
173  function hasErrors(errors) {
174    return Object.keys(errors || {}).some(function (key) {
175      return !!errors[key];
176    });
177  }
178
179  function focusFirstInvalidField(fieldOrder, fieldMap, errors) {
180    var order = fieldOrder || [];
181
182    for (var index = 0; index < order.length; index += 1) {
183      var key = order[index];
184      if (errors && errors[key]) {
185        var field = fieldMap && fieldMap[key] ? getElement(fieldMap[key].field) : null;
186        if (field && typeof field.focus === 'function') {
187          field.focus();
188        }
189        return;
190      }
191    }
192  }
193
194  function bindFieldClearOnInput(fieldMap) {
195    Object.keys(fieldMap || {}).forEach(function (key) {
196      var config = fieldMap[key];
197      var field = getElement(config.field);
198
199      if (!field) {
200        return;
201      }
202
203      ['input', 'change', 'keydown'].forEach(function (eventName) {
204        field.addEventListener(eventName, function () {
205          clearFieldError(config.field, config.error);
206        });
207      });
208    });
209  }
210
211  function autoResizeTextarea(element) {
212    if (!element) return;
213    
214    element.style.minHeight = '70px';
215    element.style.height = 'auto';
216    element.style.height = Math.max(element.scrollHeight, 70) + 'px';
217  }
218
219  function initTextareaAutoResize(selector) {
220    var textareas = document.querySelectorAll(selector);
221    
222    textareas.forEach(function(textarea) {
223      textarea.style.minHeight = '70px';
224      textarea.style.overflowY = 'hidden';
225      textarea.style.resize = 'none';
226      
227      // Auto-resize on input
228      textarea.addEventListener('input', function() {
229        autoResizeTextarea(this);
230      });
231      
232      // Initial resize
233      autoResizeTextarea(textarea);
234    });
235  }
236
237  global.CommonFormValidation = {
238    rules: RULES,
239    validateName: validateName,
240    validateEmail: validateEmail,
241    validateSubject: validateSubject,
242    validateMessage: validateMessage,
243    getLimitMessage: getLimitMessage,
244    validateForm: validateForm,
245    setFieldError: setFieldError,
246    clearFieldError: clearFieldError,
247    clearErrors: clearErrors,
248    hasErrors: hasErrors,
249    focusFirstInvalidField: focusFirstInvalidField,
250    bindFieldClearOnInput: bindFieldClearOnInput,
251    autoResizeTextarea: autoResizeTextarea,
252    initTextareaAutoResize: initTextareaAutoResize
253  };
254})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.