-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathindex.js
More file actions
189 lines (155 loc) · 5.14 KB
/
index.js
File metadata and controls
189 lines (155 loc) · 5.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/**
* Client-side file validation for file upload inputs
* Validates file size before form submission to provide immediate feedback
*/
const BYTES_IN_MB = 1024 * 1024
/**
* Format bytes to a human-readable size
* @param {number} bytes - The number of bytes
* @returns {string} Formatted file size
*/
function formatFileSize (bytes) {
if (bytes === 0) return '0 Bytes'
const mb = bytes / BYTES_IN_MB
if (mb >= 1) {
return `${mb.toFixed(1)} MB`
}
const kb = bytes / 1024
return `${kb.toFixed(1)} KB`
}
/**
* Show error message using GOV.UK Design System error pattern
* @param {HTMLInputElement} input - The file input element
* @param {string} errorMessage - The error message to display
*/
function showError (input, errorMessage) {
const formGroup = input.closest('.govuk-form-group')
if (!formGroup) return
// Add error class to form group
formGroup.classList.add('govuk-form-group--error')
// Check if error message already exists
let errorSpan = formGroup.querySelector('.govuk-error-message')
if (!errorSpan) {
// Create error message element
errorSpan = document.createElement('span')
errorSpan.className = 'govuk-error-message'
errorSpan.id = `${input.id}-error`
const visuallyHiddenSpan = document.createElement('span')
visuallyHiddenSpan.className = 'govuk-visually-hidden'
visuallyHiddenSpan.textContent = 'Error: '
errorSpan.appendChild(visuallyHiddenSpan)
// Insert error message before the file input (or after hint if present)
const hint = formGroup.querySelector('.govuk-hint')
const insertBefore = hint || input
insertBefore.parentNode.insertBefore(errorSpan, insertBefore)
}
// Update error message text (preserving the visually-hidden span)
const visuallyHidden = errorSpan.querySelector('.govuk-visually-hidden')
errorSpan.textContent = errorMessage
if (visuallyHidden) {
errorSpan.insertBefore(visuallyHidden, errorSpan.firstChild)
}
// Add error class to input
input.classList.add('govuk-file-upload--error')
// Update aria-describedby
const ariaDescribedBy = input.getAttribute('aria-describedby') || ''
if (!ariaDescribedBy.includes(errorSpan.id)) {
input.setAttribute(
'aria-describedby',
ariaDescribedBy ? `${ariaDescribedBy} ${errorSpan.id}` : errorSpan.id
)
}
}
/**
* Clear error message from a file input
* @param {HTMLInputElement} input - The file input element
*/
function clearError (input) {
const formGroup = input.closest('.govuk-form-group')
if (!formGroup) return
// Remove error class from form group
formGroup.classList.remove('govuk-form-group--error')
// Remove error message
const errorSpan = formGroup.querySelector('.govuk-error-message')
if (errorSpan) {
errorSpan.remove()
}
// Remove error class from input
input.classList.remove('govuk-file-upload--error')
// Clean up aria-describedby
const ariaDescribedBy = input.getAttribute('aria-describedby')
if (ariaDescribedBy) {
const errorId = `${input.id}-error`
const updatedAriaDescribedBy = ariaDescribedBy
.split(' ')
.filter(id => id !== errorId)
.join(' ')
if (updatedAriaDescribedBy) {
input.setAttribute('aria-describedby', updatedAriaDescribedBy)
} else {
input.removeAttribute('aria-describedby')
}
}
}
/**
* Validate file size for a file input
* @param {HTMLInputElement} input - The file input element
* @returns {boolean} Whether the file is valid
*/
function validateFileSize (input) {
const file = input.files[0]
// No file selected - clear any existing errors
if (!file) {
clearError(input)
return true
}
const maxSizeInBytes = parseInt(input.dataset.maxFileSize, 10)
// No max size specified - skip validation
if (!maxSizeInBytes) {
return true
}
// File is within size limits
if (file.size <= maxSizeInBytes) {
clearError(input)
return true
}
// File is too large - show error
const maxSizeMB = maxSizeInBytes / BYTES_IN_MB
const actualSize = formatFileSize(file.size)
const errorMessage = `The selected file must be smaller than ${maxSizeMB}MB (file is ${actualSize})`
showError(input, errorMessage)
// Clear the file input
input.value = ''
return false
}
/**
* Initialize file validation for all file inputs with data-max-file-size attribute
*/
export function initFileValidation () {
const fileInputs = document.querySelectorAll('input[type="file"][data-max-file-size]')
fileInputs.forEach(input => {
// Validate on file selection
input.addEventListener('change', (event) => {
validateFileSize(event.target)
})
})
// Also validate on form submission as a final check
document.addEventListener('submit', (event) => {
const form = event.target
const fileInputs = form.querySelectorAll('input[type="file"][data-max-file-size]')
let hasErrors = false
fileInputs.forEach(input => {
if (!validateFileSize(input)) {
hasErrors = true
}
})
if (hasErrors) {
event.preventDefault()
// Focus on the first error
const firstError = form.querySelector('.govuk-file-upload--error')
if (firstError) {
firstError.focus()
}
}
})
}