⚠️ Please complete all required fields (*) before
continuing.
⚠️ Please complete all required fields (*) before
continuing.
⚠️ Please complete all required fields (*) before
continuing.
⚠️ Please complete all required fields (*) before
continuing.
⚠️ Please complete all required fields (*) before
continuing.
⚠️ Please accept all acknowledgements before submitting.
document.addEventListener('DOMContentLoaded', function () {
var TOTAL_STEPS = 6;
var currentStep = 0;
/* ── Step navigation ──────────────────────────────────────── */
function showStep(idx) {
for (var i = 0; i < TOTAL_STEPS; i++) {
var el = document.getElementById('vol-step-' + i);
if (el) el.style.display = (i === idx) ? 'block' : 'none';
}
currentStep = idx;
updateProgress(idx);
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function updateProgress(idx) {
var pct = ((idx + 1) / TOTAL_STEPS) * 100;
var fill = document.getElementById('vol-progress-fill');
var text = document.getElementById('vol-progress-text');
if (fill) fill.style.width = pct + '%';
if (text) text.textContent = 'Step ' + (idx + 1) + ' of ' + TOTAL_STEPS;
}
/* ── Per-step validation with specific missing-field reporting ─ */
/* Field label map: CF7 name → human-readable label */
var FIELD_LABELS = {
'full-name': 'Full Name',
'short-name': 'Preferred Short Name',
'ic': 'Identification Number',
'age': 'Age',
'dob': 'Date of Birth',
'phone': 'Mobile Number',
'email': 'Email Address',
'Occupation': 'Occupation',
'home-address': 'Home Address',
'communication-method': 'Preferred Method of Communication',
'spoken-language': 'Spoken Language(s)',
'spoken-language-other': 'Spoken Language — please specify "Others"',
'emergency-contact-name': 'Emergency Contact Name',
'emergency-relationship': 'Relationship to You',
'emergency-phone': 'Emergency Contact Phone Number',
'motivation': 'What motivates you to join? (Q19)',
'contribution-areas': 'Areas you can contribute to (Q20)',
'skill-development': 'Skill areas to develop (Q21)',
'volunteer-location': 'Preferred Volunteer Location',
'availability-weekdays': 'Weekday Availability',
'availability-saturday': 'Saturday Availability',
'availability-sunday': 'Sunday Availability',
'start-date': 'Preferred Start Date',
'end-date': 'Preferred End Date',
'commitment-duration': 'Duration of Commitment',
'number-of-hours': 'Hours per Week',
'platform-know-programme': 'How did you hear about us?',
'platform-other': 'How did you hear about us? — please specify "Others"',
'areas-of-interest': 'Areas of Interest (Q33)',
'areas-of-interest-other': 'Areas of Interest — please specify "Others"',
'privacy-acknowledgement': 'Privacy Statement — please tick to accept',
'commitment-acknowledgement': 'Gaining Commitment — please tick to accept',
'indemnity-acknowledgement': 'General Indemnity — please tick to accept'
};
/* Scroll to and focus a field, then flash its border red briefly */
function focusField(name) {
/* Try direct name selector first, then name[] variant */
var el = document.querySelector('[name="' + name + '"]')
|| document.querySelector('[name="' + name + '[]"]');
if (!el) return;
/* For checkboxes/radios scroll to the parent wrapper instead */
var target = (el.type === 'checkbox' || el.type === 'radio')
? (el.closest('.vol-check-col') || el.closest('.vol-radio-row') || el.closest('.vol-radio-col') || el)
: el;
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
/* Flash red outline on the actual input / wrapper */
target.classList.add('vol-field-error');
setTimeout(function () { target.classList.remove('vol-field-error'); }, 2200);
}
/* Build and display the specific error list, or hide it if clean */
function showErrors(stepIdx, missing) {
var errEl = document.getElementById('step' + stepIdx + '-error');
if (!errEl) return;
if (missing.length === 0) {
errEl.style.display = 'none';
errEl.innerHTML = '';
return;
}
var html = '
⚠️ Please complete the following before continuing:';
missing.forEach(function (name) {
var label = FIELD_LABELS[name] || name;
html += '- ' + label + '
';
});
html += '
';
errEl.innerHTML = html;
errEl.style.display = 'block';
/* Wire click handlers on the generated links */
errEl.querySelectorAll('.vol-error-link').forEach(function (link) {
link.addEventListener('click', function (e) {
e.preventDefault();
focusField(this.getAttribute('data-field'));
});
});
}
function validateStep(stepIdx) {
var missing = [];
/* Is a text / number / date / select input filled? */
function filled(name) {
var el = document.querySelector('[name="' + name + '"]');
return el && el.value.trim() !== '';
}
/* Does a checkbox group have at least one option ticked?
CF7 may render the name as "name" or "name[]" — check both. */
function checkboxAnswered(name) {
var sel = 'input[type="checkbox"][name="' + name + '"]:checked,'
+ 'input[type="checkbox"][name="' + name + '[]"]:checked';
return document.querySelectorAll(sel).length > 0;
}
/* Collect missing rather than just flagging ok = false */
function require(name, check) {
if (!check) missing.push(name);
}
if (stepIdx === 0) {
require('full-name', filled('full-name'));
require('short-name', filled('short-name'));
require('ic', filled('ic'));
require('age', filled('age'));
require('dob', filled('dob'));
/* gender & nationality: always answered via default:1 — no check needed */
/* image-upload: validated server-side by CF7 */
}
if (stepIdx === 1) {
require('phone', filled('phone'));
require('email', filled('email'));
require('Occupation', filled('Occupation'));
require('home-address', filled('home-address'));
require('communication-method', checkboxAnswered('communication-method'));
require('spoken-language', checkboxAnswered('spoken-language'));
/* If "Others" ticked for spoken language, the specify field is required */
var spokenOthers = document.querySelectorAll(
'input[type="checkbox"][name="spoken-language"][value="Others"]:checked,' +
'input[type="checkbox"][name="spoken-language[]"][value="Others"]:checked'
);
if (spokenOthers.length > 0) require('spoken-language-other', filled('spoken-language-other'));
}
if (stepIdx === 2) {
require('emergency-contact-name', filled('emergency-contact-name'));
require('emergency-relationship', filled('emergency-relationship'));
require('emergency-phone', filled('emergency-phone'));
}
if (stepIdx === 3) {
require('motivation', checkboxAnswered('motivation'));
require('contribution-areas', checkboxAnswered('contribution-areas'));
require('skill-development', checkboxAnswered('skill-development'));
require('availability-weekdays', checkboxAnswered('availability-weekdays'));
require('availability-saturday', checkboxAnswered('availability-saturday'));
require('availability-sunday', checkboxAnswered('availability-sunday'));
require('number-of-hours', checkboxAnswered('number-of-hours'));
require('volunteer-location', filled('volunteer-location'));
require('commitment-duration', filled('commitment-duration'));
require('start-date', filled('start-date'));
require('end-date', filled('end-date'));
require('platform-know-programme', filled('platform-know-programme'));
/* past-experience: always answered via default:1 — no check needed */
/* If "Others" selected for heard-about-us, the specify field is required */
var platformEl = document.getElementById('platform-know-programme');
if (platformEl && platformEl.value === 'Others') {
require('platform-other', filled('platform-other'));
}
}
if (stepIdx === 4) {
require('areas-of-interest', checkboxAnswered('areas-of-interest'));
/* If "Others" ticked for areas of interest, the specify field is required */
var aoiOthers = document.querySelectorAll(
'input[type="checkbox"][name="areas-of-interest"][value="Others"]:checked,' +
'input[type="checkbox"][name="areas-of-interest[]"][value="Others"]:checked'
);
if (aoiOthers.length > 0) require('areas-of-interest-other', filled('areas-of-interest-other'));
}
if (stepIdx === 5) {
['privacy-acknowledgement', 'commitment-acknowledgement', 'indemnity-acknowledgement'].forEach(function (n) {
var el = document.querySelector('input[name="' + n + '"]');
require(n, el && el.checked);
});
}
showErrors(stepIdx, missing);
return missing.length === 0;
}
/* ── Wire Next buttons ────────────────────────────────────── */
document.querySelectorAll('.vol-btn-next').forEach(function (btn) {
btn.addEventListener('click', function () {
var step = parseInt(this.getAttribute('data-step'), 10);
if (!validateStep(step)) return;
if (currentStep 0) showStep(currentStep - 1);
});
});
/* ── Block CF7 submit if final step is invalid ────────────── */
var cf7Form = document.querySelector('.wpcf7-form');
if (cf7Form) {
cf7Form.addEventListener('submit', function (e) {
if (!validateStep(5)) {
e.preventDefault();
e.stopPropagation();
}
}, true);
}
/* ── Highlight selected radio / checkbox list items ──────── */
function syncListItemState(input) {
var item = input.closest('.wpcf7-list-item');
if (!item) return;
if (input.type === 'radio') {
document.querySelectorAll('input[type="radio"][name="' + input.name + '"]').forEach(function (r) {
var li = r.closest('.wpcf7-list-item');
if (li) li.classList.toggle('is-checked', r.checked);
});
} else if (input.type === 'checkbox') {
item.classList.toggle('is-checked', input.checked);
}
}
document.addEventListener('change', function (e) {
var t = e.target;
if (t && (t.type === 'radio' || t.type === 'checkbox')) syncListItemState(t);
});
/* Sync initial state for any pre-checked inputs (e.g. default:1 radios) */
document.querySelectorAll(
'.vol-radio-col input[type="radio"], .vol-radio-row input[type="radio"], .vol-check-col input[type="checkbox"]'
).forEach(syncListItemState);
/* ── Clear error banners on successful CF7 submit ─────────── */
document.addEventListener('wpcf7submit', function () {
for (var i = 0; i < TOTAL_STEPS; i++) {
var err = document.getElementById('step' + i + '-error');
if (err) { err.style.display = 'none'; err.innerHTML = ''; }
}
}, false);
/* ── Conditional reveal: Q32 "Where did you hear" → Others ── */
var platformSelect = document.getElementById('platform-know-programme');
var platformOtherWrap = document.getElementById('platform-other-wrap');
if (platformSelect && platformOtherWrap) {
platformSelect.addEventListener('change', function () {
var show = this.value === 'Others';
platformOtherWrap.style.display = show ? 'block' : 'none';
if (!show) {
var inp = document.getElementById('platform-other');
if (inp) inp.value = '';
}
});
}
/* ── Conditional reveals: checkbox "Others" sub-fields ──────
Covers Q14 (Spoken Language) and Q33 (Areas of Interest) */
document.addEventListener('change', function (e) {
var t = e.target;
if (!t || t.type !== 'checkbox' || t.value !== 'Others') return;
var name = t.name.replace('[]', '');
if (name === 'spoken-language') {
var wrap = document.getElementById('spoken-other-wrap');
var inp = document.getElementById('spoken-language-other');
if (wrap) wrap.style.display = t.checked ? 'block' : 'none';
if (!t.checked && inp) inp.value = '';
}
if (name === 'areas-of-interest') {
var wrap = document.getElementById('aoi-other-wrap');
var inp = document.getElementById('areas-of-interest-other');
if (wrap) wrap.style.display = t.checked ? 'block' : 'none';
if (!t.checked && inp) inp.value = '';
}
});
document.querySelectorAll('.vol-bold-key .wpcf7-list-item label').forEach(function (label) {
var input = label.querySelector('input');
var textNode = null;
label.childNodes.forEach(function (node) {
if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '') {
textNode = node;
}
});
var spanNode = label.querySelector('span:not(.wpcf7-list-item-label)') || label.querySelector('span');
var target = textNode || spanNode;
if (!target) return;
var raw = target.textContent || target.innerText || '';
var sep = ' \u2014 ';
var idx = raw.indexOf(sep);
if (idx === -1) return;
var keyword = raw.slice(0, idx);
var rest = raw.slice(idx);
var b = document.createElement('b');
b.textContent = keyword;
var tail = document.createTextNode(rest);
if (textNode) {
label.replaceChild(tail, textNode);
label.insertBefore(b, tail);
} else if (spanNode) {
spanNode.innerHTML = '';
spanNode.appendChild(b);
spanNode.appendChild(document.createTextNode(rest));
}
});
/* ── Initialise ───────────────────────────────────────────── */
showStep(0);
});