Skip to content

Commit 306010f

Browse files
committed
Fix bug with checkbox groups where accented characters and spaces are used in the options value
1 parent 406cf3e commit 306010f

File tree

1 file changed

+29
-10
lines changed

1 file changed

+29
-10
lines changed

modules/custom/wxt_ext/wxt_ext_webform/js/webform_checkboxes_group.js

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@
6363
function wire(v) {
6464
const groups = discoverGroups();
6565

66+
// Make jQuery Validate prefer element.id over name (fixes accented name issues)
67+
v.idOrName = function (el) {
68+
return el.id || (el.name ? el.name.replace(/[^\w\-]+/g, '_') : '');
69+
};
70+
6671
// 1) Register one jQuery Validate "group" per checkbox base.
6772
// This makes jQuery Validate treat multiple field names as one logical group
6873
// when deciding which single label/entry to render.
@@ -72,10 +77,12 @@
7277
v.settings.groups[base] = groups[base].join(' ');
7378
});
7479

75-
// Precompute the "first" field name for each base; duplicates will be suppressed.
76-
const firstOf = {};
80+
// Precompute the FIRST field *id* of each base so we can place a single inline label.
81+
const firstIdOf = {};
7782
Object.keys(groups).forEach((base) => {
78-
firstOf[base] = groups[base][0];
83+
const firstName = groups[base][0];
84+
const $el = $form.find('[name="' + CSS.escape(firstName) + '"]').first();
85+
firstIdOf[base] = $el.attr('id') || '';
7986
});
8087

8188
// Shadow a local baseOf so inner functions have it in scope (performance/readability).
@@ -90,8 +97,10 @@
9097

9198
// If this is part of a checkbox base and it's NOT the first item,
9299
// skip placing the label entirely (the "first" item will get it).
93-
if (base && name !== firstOf[base]) {
94-
return;
100+
if (base) {
101+
const id = element.attr('id') || '';
102+
// Only place for the FIRST id in the group, skip others to avoid duplicates.
103+
if (firstIdOf[base] && id !== firstIdOf[base]) return;
95104
}
96105
return origPlace(error, element);
97106
};
@@ -104,14 +113,16 @@
104113
const seen = new Set();
105114
const list = [];
106115
const map = {};
116+
107117
validator.errorList.forEach((item) => {
108-
const nm = item.element && item.element.name;
109-
// Fall back to name for non-array fields.
118+
const el = item.element;
119+
const nm = el && el.name;
120+
const id = el && el.id;
110121
const base = baseOf(nm) || nm;
122+
const dedupeKey = base + '::' + (id || '');
111123

112-
// Keep the first error per base, drop the rest.
113-
if (!seen.has(base)) {
114-
seen.add(base);
124+
if (!seen.has(dedupeKey)) {
125+
seen.add(dedupeKey);
115126
list.push(item);
116127
if (nm) map[nm] = item.message;
117128
}
@@ -121,6 +132,14 @@
121132
validator.errorList = list;
122133
validator.errorMap = map;
123134

135+
// Clean up any duplicate labels that may exist (caused earlier by accented names)
136+
const seenLabelFor = {};
137+
$(formEl).find('label.error[for]').each(function () {
138+
const f = $(this).attr('for');
139+
if (seenLabelFor[f]) $(this).remove();
140+
else seenLabelFor[f] = true;
141+
});
142+
124143
// Allow any original invalidHandler (including WET's) to run with the reduced list.
125144
return origInvalid.call(this, formEl, validator);
126145
};

0 commit comments

Comments
 (0)