Skip to content

Commit cc357bd

Browse files
Merge pull request #460 from bcgov/BCHEP-617
fix(BCHEP-617): override formio file component bootstrapper
2 parents 73d1195 + 104994e commit cc357bd

File tree

3 files changed

+370
-78
lines changed

3 files changed

+370
-78
lines changed
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
// Override for the formio bootstrap file template.
2+
//
3+
// The original template embeds ctx.component.description (which is raw HTML) directly
4+
// into a <span class="sr-only"> that lives inside an <a> tag. When the description
5+
// contains block-level elements such as <ul>/<li> the browser's HTML parser
6+
// considers the nesting invalid and hoists those elements outside every inline
7+
// ancestor, making them fully visible even though they should be hidden.
8+
//
9+
// Fix: strip all HTML tags from the description before putting it into the sr-only
10+
// span so the text remains purely inline.
11+
12+
function stripHtml(html) {
13+
if (!html) return '';
14+
// Use the DOM when available (browser), otherwise fall back to a simple regex.
15+
if (typeof document !== 'undefined') {
16+
const tmp = document.createElement('div');
17+
tmp.innerHTML = html;
18+
return tmp.textContent || tmp.innerText || '';
19+
}
20+
return html.replace(/<[^>]*>/g, '');
21+
}
22+
23+
export const overrideFileTemplate = (ctx) => {
24+
var __t,
25+
__p = '',
26+
__j = Array.prototype.join;
27+
function print() {
28+
__p += __j.call(arguments, '');
29+
}
30+
31+
if (ctx.options.vpat) {
32+
__p +=
33+
'\n <span tabindex="-1" class="sr-only" id="invisible-' +
34+
((__t = ctx.instance.id) == null ? '' : __t) +
35+
'-' +
36+
((__t = ctx.component.key) == null ? '' : __t) +
37+
'"></span>\n';
38+
}
39+
__p += '\n';
40+
if (!ctx.self.imageUpload) {
41+
__p += '\n ';
42+
if (ctx.options.vpat) {
43+
__p +=
44+
'\n <div>' +
45+
((__t =
46+
!ctx.component.filePattern || ctx.component.filePattern === '*'
47+
? 'Any file types are allowed'
48+
: ctx.t('Allowed file types: ') + ctx.component.filePattern) == null
49+
? ''
50+
: __t) +
51+
'</div>\n ';
52+
}
53+
__p +=
54+
'\n <ul class="list-group list-group-striped">\n <li class="list-group-item list-group-header hidden-xs hidden-sm">\n <div class="row">\n ';
55+
if (!ctx.disabled) {
56+
__p += '\n <div class="col-md-1"></div>\n ';
57+
}
58+
__p += '\n <div class="col-md-';
59+
if (ctx.self.hasTypes) {
60+
__p += '7';
61+
} else {
62+
__p += '9';
63+
}
64+
__p +=
65+
'"><strong>' +
66+
((__t = ctx.t('File Name')) == null ? '' : __t) +
67+
'</strong></div>\n <div class="col-md-2"><strong>' +
68+
((__t = ctx.t('Size')) == null ? '' : __t) +
69+
'</strong></div>\n ';
70+
if (ctx.self.hasTypes) {
71+
__p +=
72+
'\n <div class="col-md-2"><strong>' +
73+
((__t = ctx.t('Type')) == null ? '' : __t) +
74+
'</strong></div>\n ';
75+
}
76+
__p += '\n </div>\n </li>\n ';
77+
ctx.files.forEach(function (file) {
78+
__p += '\n <li class="list-group-item">\n <div class="row">\n ';
79+
if (!ctx.disabled) {
80+
__p +=
81+
'\n <div class="col-md-1"><i tabindex="0" class="' +
82+
((__t = ctx.iconClass('remove')) == null ? '' : __t) +
83+
'" ref="removeLink"></i></div>\n ';
84+
}
85+
__p += '\n <div class="col-md-';
86+
if (ctx.self.hasTypes) {
87+
__p += '7';
88+
} else {
89+
__p += '9';
90+
}
91+
__p += '">\n ';
92+
if (ctx.component.uploadOnly) {
93+
__p += '\n ' + ((__t = file.originalName || file.name) == null ? '' : __t) + '\n ';
94+
} else {
95+
__p +=
96+
'\n <a href="' +
97+
((__t = file.url || '#') == null ? '' : __t) +
98+
'" target="_blank" ref="fileLink">\n <span class="sr-only">' +
99+
((__t = ctx.t('Press to open ')) == null ? '' : __t) +
100+
'</span>' +
101+
((__t = file.originalName || file.name) == null ? '' : __t) +
102+
'\n </a>\n ';
103+
}
104+
__p +=
105+
'\n </div>\n <div class="col-md-2">' +
106+
((__t = ctx.fileSize(file.size)) == null ? '' : __t) +
107+
'</div>\n ';
108+
if (ctx.self.hasTypes && !ctx.disabled) {
109+
__p +=
110+
'\n <div class="col-md-2">\n <select class="file-type" ref="fileType">\n ';
111+
ctx.component.fileTypes.map(function (type) {
112+
__p += '\n <option class="test" value="' + ((__t = type.value) == null ? '' : __t) + '" ';
113+
if (type.label === file.fileType) {
114+
__p += 'selected="selected"';
115+
}
116+
__p += '>' + ((__t = ctx.t(type.label)) == null ? '' : __t) + '</option>\n ';
117+
});
118+
__p += '\n </select>\n </div>\n ';
119+
}
120+
if (ctx.self.hasTypes && ctx.disabled) {
121+
__p += '\n <div class="col-md-2">' + ((__t = file.fileType) == null ? '' : __t) + '</div>\n ';
122+
}
123+
__p += '\n </div>\n </li>\n ';
124+
});
125+
__p += '\n </ul>\n';
126+
} else {
127+
__p += '\n <div>\n ';
128+
ctx.files.forEach(function (file) {
129+
__p +=
130+
'\n <div>\n <span>\n <img ref="fileImage" src="" alt="' +
131+
((__t = file.originalName || file.name) == null ? '' : __t) +
132+
'" style="width:' +
133+
((__t = ctx.component.imageSize) == null ? '' : __t) +
134+
'px">\n ';
135+
if (!ctx.disabled) {
136+
__p +=
137+
'\n <i tabindex="0" class="' +
138+
((__t = ctx.iconClass('remove')) == null ? '' : __t) +
139+
'" ref="removeLink"></i>\n ';
140+
}
141+
__p += '\n </span>\n </div>\n ';
142+
});
143+
__p += '\n </div>\n';
144+
}
145+
__p += '\n';
146+
if (!ctx.disabled && (ctx.component.multiple || !ctx.files.length)) {
147+
__p += '\n ';
148+
if (ctx.self.useWebViewCamera) {
149+
__p +=
150+
'\n <div class="fileSelector">\n <button class="btn btn-primary" ref="galleryButton"><i class="fa fa-book"></i> ' +
151+
((__t = ctx.t('Gallery')) == null ? '' : __t) +
152+
'</button>\n <button class="btn btn-primary" ref="cameraButton"><i class="fa fa-camera"></i> ' +
153+
((__t = ctx.t('Camera')) == null ? '' : __t) +
154+
'</button>\n </div>\n ';
155+
} else if (!ctx.self.cameraMode) {
156+
__p +=
157+
'\n <div class="fileSelector" ref="fileDrop" ' +
158+
((__t = ctx.fileDropHidden ? 'hidden' : '') == null ? '' : __t) +
159+
'>\n <i class="' +
160+
((__t = ctx.iconClass('cloud-upload')) == null ? '' : __t) +
161+
'"></i> ' +
162+
((__t = ctx.t('Drop files to attach,')) == null ? '' : __t) +
163+
'\n ';
164+
if (ctx.self.imageUpload && ctx.component.webcam) {
165+
__p +=
166+
'\n <a href="#" ref="toggleCameraMode"><i class="fa fa-camera"></i> ' +
167+
((__t = ctx.t('use camera')) == null ? '' : __t) +
168+
'</a>\n ';
169+
}
170+
// --- KEY FIX: strip HTML from description before embedding in sr-only span ---
171+
const plainDescription = stripHtml(ctx.component.description);
172+
const filePatternText =
173+
!ctx.component.filePattern || ctx.component.filePattern === '*'
174+
? 'Any file types are allowed'
175+
: ctx.t('Allowed file types: ') + ctx.component.filePattern;
176+
const srOnlyText = ctx.t(
177+
'Browse to attach file for ' +
178+
ctx.component.label +
179+
'. ' +
180+
(plainDescription ? plainDescription + '. ' : '') +
181+
filePatternText,
182+
);
183+
184+
__p +=
185+
'\n ' +
186+
((__t = ctx.t('or')) == null ? '' : __t) +
187+
'\n <a href="#" ref="fileBrowse" class="browse">\n ' +
188+
((__t = ctx.t('browse')) == null ? '' : __t) +
189+
'\n <span class="sr-only">\n ' +
190+
((__t = srOnlyText) == null ? '' : __t) +
191+
'\n </span>\n </a>\n <div ref="fileProcessingLoader" class="loader-wrapper">\n <div class="loader text-center"></div>\n </div>\n </div>\n ';
192+
} else {
193+
__p +=
194+
'\n <div class="video-container">\n <video class="video" autoplay="true" ref="videoPlayer" tabindex="-1"></video>\n </div>\n <button class="btn btn-primary" ref="takePictureButton"><i class="fa fa-camera"></i> ' +
195+
((__t = ctx.t('Take Picture')) == null ? '' : __t) +
196+
'</button>\n <button class="btn btn-primary" ref="toggleCameraMode">' +
197+
((__t = ctx.t('Switch to file upload')) == null ? '' : __t) +
198+
'</button>\n ';
199+
}
200+
__p += '\n';
201+
}
202+
__p += '\n';
203+
ctx.statuses.forEach(function (status) {
204+
__p +=
205+
'\n <div class="file ' +
206+
((__t = ctx.statuses.status === 'error' ? ' has-error' : '') == null ? '' : __t) +
207+
'">\n <div class="row">\n <div class="fileName col-form-label col-sm-10">' +
208+
((__t = status.originalName) == null ? '' : __t) +
209+
'\n <i class="' +
210+
((__t = ctx.iconClass('remove')) == null ? '' : __t) +
211+
'" ref="fileStatusRemove">\n <span class="sr-only">' +
212+
((__t = ctx.t('Remove button. Press to remove ' + status.originalName || status.name + '.')) == null ? '' : __t) +
213+
'</span>\n <span class="sr-only">' +
214+
((__t = status.message ? status.message.replace(';', '.') : '') == null ? '' : __t) +
215+
'</span>\n </i>\n </div>\n <div class="fileSize col-form-label col-sm-2 text-right">' +
216+
((__t = ctx.fileSize(status.size)) == null ? '' : __t) +
217+
'</div>\n </div>\n <div class="row">\n <div class="col-sm-12">\n ';
218+
if (status.status === 'progress') {
219+
__p +=
220+
'\n <div class="progress">\n <div class="progress-bar" role="progressbar" aria-valuenow="' +
221+
((__t = status.progress) == null ? '' : __t) +
222+
'" aria-valuemin="0" aria-valuemax="100" style="width: ' +
223+
((__t = status.progress) == null ? '' : __t) +
224+
'%">\n <span class="sr-only">' +
225+
((__t = status.progress) == null ? '' : __t) +
226+
'% ' +
227+
((__t = ctx.t('Complete')) == null ? '' : __t) +
228+
'</span>\n </div>\n </div>\n ';
229+
} else if (status.status === 'error') {
230+
__p +=
231+
'\n <div class="alert alert-danger bg-' +
232+
((__t = status.status) == null ? '' : __t) +
233+
'">' +
234+
((__t = ctx.t(status.message)) == null ? '' : __t) +
235+
'</div>\n ';
236+
} else {
237+
__p +=
238+
'\n <div class="bg-' +
239+
((__t = status.status) == null ? '' : __t) +
240+
'">' +
241+
((__t = ctx.t(status.message)) == null ? '' : __t) +
242+
'</div>\n ';
243+
}
244+
__p += '\n </div>\n </div>\n </div>\n';
245+
});
246+
__p += '\n';
247+
if (!ctx.component.storage || ctx.support.hasWarning) {
248+
__p += '\n <div class="alert alert-warning">\n ';
249+
if (!ctx.component.storage) {
250+
__p +=
251+
'\n <p>' +
252+
((__t = ctx.t('No storage has been set for this field. File uploads are disabled until storage is set up.')) ==
253+
null
254+
? ''
255+
: __t) +
256+
'</p>\n ';
257+
}
258+
__p += '\n ';
259+
if (!ctx.support.filereader) {
260+
__p +=
261+
'\n <p>' + ((__t = ctx.t('File API & FileReader API not supported.')) == null ? '' : __t) + '</p>\n ';
262+
}
263+
__p += '\n ';
264+
if (!ctx.support.formdata) {
265+
__p += '\n <p>' + ((__t = ctx.t("XHR2's FormData is not supported.")) == null ? '' : __t) + '</p>\n ';
266+
}
267+
__p += '\n ';
268+
if (!ctx.support.progress) {
269+
__p +=
270+
'\n <p>' + ((__t = ctx.t("XHR2's upload progress isn't supported.")) == null ? '' : __t) + '</p>\n ';
271+
}
272+
__p += '\n </div>\n';
273+
}
274+
__p += '\n';
275+
return __p;
276+
};

0 commit comments

Comments
 (0)