Update field validation
August 11, 2025 · AI Conversations
$(this).siblings('input').trigger('click'); // update GF conditional logic first
var $scrollTo = null;
var $sourceBlockEl = $(this).closest('.gfield');
var $nextBlockEl = $sourceBlockEl.next();
var headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : $('#et-main-area').offset().top;
// Only treat as "correct" if not flagged incorrect AND gating allows
var isCorrectClick = ((($(this).attr('onclick') || '').indexOf('incorrect') === -1) &&
(typeof isOverride !== 'undefined' && isOverride ||
(typeof availableProducts !== 'undefined' && availableProducts.length > 0)));
// — helpers —
function isIncorrect($f){
return $f.hasClass('gfield_error') ||
$f.hasClass('gquiz-field-incorrect') ||
$f.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').length > 0;
}
function isRequired($f){
return $f.hasClass('gfield_contains_required') ||
$f.find('[aria-required="true"], [required]').length > 0;
}
function hasInputContainer($f){
return $f.find('.ginput_container').length > 0;
}
function isAnswered($f){
if (!hasInputContainer($f)) return true; // sections/HTML treated as “answered/skip”
// choices
if ($f.find('.ginput_container_choice, .ginput_container_radio, .ginput_container_checkbox').length) {
return $f.find('input:radio:checked, input:checkbox:checked').length > 0;
}
// text-ish
var textFilled = $f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea')
.filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
var selectFilled = $f.find('select').filter(function(){ var v=$(this).val(); return v !== '' && v !== null; }).length > 0;
var dateTimeFilled = $f.find('input[type="date"], input[type="time"]')
.filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
return textFilled || selectFilled || dateTimeFilled;
}
// — end helpers —
if (isCorrectClick) {
// 1) Go UP to nearest previous field that is incorrect OR (required & unanswered)
var $visibleFields = $('.gfield:visible');
var idx = $visibleFields.index($sourceBlockEl);
for (var i = idx – 1; i >= 0; i–) {
var $f = $visibleFields.eq(i);
if (isIncorrect($f) || (isRequired($f) && !isAnswered($f))) {
$scrollTo = $f;
break;
}
}
// For tall targets on mobile, try to focus a meaningful sub-element
if ($scrollTo && $scrollTo.outerHeight() > $(window).outerHeight()) {
var $sub = $scrollTo.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
if (!$sub.length) {
$sub = $scrollTo.find('input, select, textarea').filter(':visible').first();
}
if ($sub.length) $scrollTo = $sub;
headerHeight = $(window).outerHeight() / 4;
}
// 2) If no target above, fall back to normal DOWN auto-scroll
if (!$scrollTo) {
if ($nextBlockEl.is(':visible') && !isAnswered($nextBlockEl)) {
$scrollTo = $nextBlockEl;
} else {
// first visible unanswered anywhere (required OR optional)
$('.gfield:visible').each(function(){
var $f = $(this);
if (!hasInputContainer($f)) return; // skip non-input sections
if (!isAnswered($f)) { $scrollTo = $f; return false; }
});
if (!$scrollTo) {
// nothing left unanswered → center submit
$scrollTo = $('.gform_footer');
headerHeight = headerHeight + ($(window).outerHeight() / 4);
}
}
}
}
// No scroll on incorrect clicks
if ($scrollTo && $scrollTo.length) {
var elOffset = $scrollTo.is(':visible')
? $scrollTo.offset().top
: $sourceBlockEl.offset().top + $sourceBlockEl.outerHeight();
$('html, body').animate({ scrollTop: elOffset – headerHeight }, 1000);
}
});
above script is working fine just change one condition it should scroll up to nearlest question if its only required field
Replace this in your loop:
// OLD:
if (isIncorrect($f) || (isRequired($f) && !isAnswered($f))) {
$scrollTo = $f;
break;
}
with:
// NEW: only required fields qualify (incorrect OR unanswered)
if (isRequired($f) && (isIncorrect($f) || !isAnswered($f))) {
$scrollTo = $f;
break;
}
Everything else can stay as-is.
If you *also* want the DOWN fallback to only consider required fields, tweak these (optional):
// next block
if ($nextBlockEl.is(':visible') && isRequired($nextBlockEl) && !isAnswered($nextBlockEl)) { ... }
// first unanswered anywhere
$('.gfield:visible').each(function(){
var $f = $(this);
if (!hasInputContainer($f)) return;
if (isRequired($f) && !isAnswered($f)) { $scrollTo = $f; return false; }
});
Asian or Asian British
Black, African, Caribbean or Black British
Middle Eastern
Mixed or multiple ethnicities (including Asian, Black, or Middle Eastern backgrounds)
White (including White British, White Irish, and other White backgrounds)
Prefer not to say (this may affect the prescribing decisions)
We ask this because healthy BMI ranges can be different for different ethnic groups.
still it scrolls up for like this unrequired question
Fix it by (a) only scanning **required** fields, and (b) scanning **below the current field** (not from the top).
Replace your fallback block with this:
// 2) If no target above, fall back to DOWN auto-scroll (required-only, below current)
if (!$scrollTo) {
// a) immediate next block if required+unanswered
if ($nextBlockEl.is(':visible') && isRequired($nextBlockEl) && !isAnswered($nextBlockEl)) {
$scrollTo = $nextBlockEl;
} else {
// b) first REQUIRED unanswered field BELOW current
var $visibleFields = $('.gfield:visible');
for (var j = idx + 1; j < $visibleFields.length; j++) {
var $f2 = $visibleFields.eq(j);
if (!hasInputContainer($f2)) continue;
if (isRequired($f2) && !isAnswered($f2)) { $scrollTo = $f2; break; }
}
// c) if none, go to submit (do NOT jump to optional fields)
if (!$scrollTo) {
$scrollTo = $('.gform_footer');
headerHeight = headerHeight + ($(window).outerHeight() / 4);
}
}
}
(Keep your earlier UP-scan condition as `isRequired($f) && (isIncorrect($f) || !isAnswered($f))`.)
(function ($) {
// Rebind safely (important when GF re-renders DOM)
$(document)
.off('click.gfAutoScroll', '.gfield_choice:not(:last) label')
.on('click.gfAutoScroll', '.gfield_choice:not(:last) label', function () {
// Let GF update conditional logic first
$(this).siblings('input').trigger('click');
var $sourceBlockEl = $(this).closest('.gfield');
var $form = $sourceBlockEl.closest('form');
if (!$form.length) $form = $(document); // fallback
// next visible field (skip hidden nodes)
var $nextBlockEl = $sourceBlockEl.nextAll('.gfield:visible').first();
function computeHeaderHeight() {
return $('#et_mobile_nav_menu').is(':visible') ? 0 : ($('#et-main-area').offset() ? $('#et-main-area').offset().top : 0);
}
var headerHeight = computeHeaderHeight();
// Only treat as "correct" if not flagged incorrect AND gating allows
var isCorrectClick =
((($(this).attr('onclick') || '').indexOf('incorrect') === -1)) &&
((typeof isOverride !== 'undefined' && isOverride) ||
(typeof availableProducts !== 'undefined' && availableProducts.length > 0));
// --- helpers (scoped to this form) ---
function isIncorrect($f) {
return $f.hasClass('gfield_error') ||
$f.hasClass('gquiz-field-incorrect') ||
$f.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').length > 0;
}
function isRequired($f) {
return $f.hasClass('gfield_contains_required') ||
$f.find('[aria-required="true"], [required]').length > 0;
}
function hasInputContainer($f) {
return $f.find('.ginput_container').length > 0;
}
function isAnswered($f) {
if (!hasInputContainer($f)) return true; // non-input sections treated as answered/skip
// choices
if ($f.find('.ginput_container_choice, .ginput_container_radio, .ginput_container_checkbox').length) {
// consider "Other" radio with text as answered only when radio is selected
var anyChecked = $f.find('input:radio:checked, input:checkbox:checked').length > 0;
if (anyChecked) return true;
}
// text-ish
var textFilled = $f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea')
.filter(function () {
var v = $.trim($(this).val());
return v !== '' && v.toLowerCase() !== 'other'; // avoid placeholder-like "Other"
}).length > 0;
var selectFilled = $f.find('select').filter(function () {
var v = $(this).val();
return v !== '' && v !== null;
}).length > 0;
var dateTimeFilled = $f.find('input[type="date"], input[type="time"]')
.filter(function () { return $.trim($(this).val()) !== ''; }).length > 0;
return textFilled || selectFilled || dateTimeFilled;
}
// --- end helpers ---
var $scrollTo = $(); // empty jQuery
if (isCorrectClick) {
// VISIBLE fields in this form
var $visibleFields = $form.find('.gfield:visible');
var idx = $visibleFields.index($sourceBlockEl);
// 1) Go UP to nearest previous field that is REQUIRED and (incorrect OR unanswered)
for (var i = idx - 1; i >= 0; i--) {
var $f = $visibleFields.eq(i);
if (!isRequired($f)) continue;
if (isIncorrect($f) || !isAnswered($f)) { $scrollTo = $f; break; }
}
// For tall targets on mobile, focus a meaningful sub-element
if ($scrollTo.length && $scrollTo.outerHeight() > $(window).outerHeight()) {
var $sub = $scrollTo.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
if (!$sub.length) {
$sub = $scrollTo.find('input, select, textarea').filter(':visible').first();
}
if ($sub.length) $scrollTo = $sub;
headerHeight = $(window).outerHeight() / 4; // give more headroom for tall targets
}
// 2) If no target above, fall back to DOWN auto-scroll (REQUIRED-only, below current)
if (!$scrollTo.length) {
if ($nextBlockEl.length && isRequired($nextBlockEl) && !isAnswered($nextBlockEl)) {
$scrollTo = $nextBlockEl;
} else {
for (var j = idx + 1; j < $visibleFields.length; j++) {
var $f2 = $visibleFields.eq(j);
if (!hasInputContainer($f2)) continue;
if (isRequired($f2) && !isAnswered($f2)) { $scrollTo = $f2; break; }
}
// If none remain, go to the submit/footer (do NOT jump to optional fields)
if (!$scrollTo.length) {
$scrollTo = $form.find('.gform_footer:visible').first();
if (!$scrollTo.length) $scrollTo = $('.gform_footer:visible').first(); // global fallback
headerHeight = computeHeaderHeight() + ($(window).outerHeight() / 4);
}
}
}
}
// No scroll on incorrect clicks
if ($scrollTo && $scrollTo.length) {
var elOffset = $scrollTo.is(':visible')
? $scrollTo.offset().top
: ($sourceBlockEl.offset().top + $sourceBlockEl.outerHeight());
$('html, body').animate({ scrollTop: elOffset - headerHeight }, 1000);
}
});
})(jQuery);
(function ($) {
// Rebind safely (GF can re-render DOM)
$(document)
.off('click.gfAutoScroll', '.gfield_choice:not(:last) label')
.on('click.gfAutoScroll', '.gfield_choice:not(:last) label', function () {
// Let GF update conditional logic first
$(this).siblings('input').trigger('click');
var $sourceBlockEl = $(this).closest('.gfield');
var $form = $sourceBlockEl.closest('form');
if (!$form.length) $form = $(document); // fallback
// next visible field (skip hidden)
var $nextBlockEl = $sourceBlockEl.nextAll('.gfield:visible').first();
function computeHeaderHeight() {
return $('#et_mobile_nav_menu').is(':visible') ? 0 : ($('#et-main-area').offset() ? $('#et-main-area').offset().top : 0);
}
var headerHeight = computeHeaderHeight();
// Only treat as "correct" if not flagged incorrect AND gating allows
var isCorrectClick =
((($(this).attr('onclick') || '').indexOf('incorrect') === -1)) &&
((typeof isOverride !== 'undefined' && isOverride) ||
(typeof availableProducts !== 'undefined' && availableProducts.length > 0));
// --- helpers (scoped to this form) ---
function isIncorrect($f) {
return $f.hasClass('gfield_error') ||
$f.hasClass('gquiz-field-incorrect') ||
$f.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').length > 0;
}
function isRequired($f) {
return $f.hasClass('gfield_contains_required') ||
$f.find('[aria-required="true"], [required]').length > 0;
}
function hasInputContainer($f) {
return $f.find('.ginput_container').length > 0;
}
function isAnswered($f) {
if (!hasInputContainer($f)) return true; // non-input sections treated as answered/skip
// choices
if ($f.find('.ginput_container_choice, .ginput_container_radio, .ginput_container_checkbox').length) {
var anyChecked = $f.find('input:radio:checked, input:checkbox:checked').length > 0;
if (anyChecked) return true;
}
// text-ish
var textFilled = $f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea')
.filter(function () {
var v = $.trim($(this).val());
return v !== '' && v.toLowerCase() !== 'other'; // ignore placeholder-like "Other"
}).length > 0;
var selectFilled = $f.find('select').filter(function () {
var v = $(this).val();
return v !== '' && v !== null;
}).length > 0;
var dateTimeFilled = $f.find('input[type="date"], input[type="time"]')
.filter(function () { return $.trim($(this).val()) !== ''; }).length > 0;
return textFilled || selectFilled || dateTimeFilled;
}
// --- end helpers ---
var $scrollTo = $(); // empty jQuery
if (isCorrectClick) {
var $visibleFields = $form.find('.gfield:visible');
var idx = $visibleFields.index($sourceBlockEl);
// 1) UP: only REQUIRED fields (incorrect OR unanswered)
for (var i = idx - 1; i >= 0; i--) {
var $f = $visibleFields.eq(i);
if (!isRequired($f)) continue;
if (isIncorrect($f) || !isAnswered($f)) { $scrollTo = $f; break; }
}
// For tall targets, aim at a meaningful sub-element
if ($scrollTo.length && $scrollTo.outerHeight() > $(window).outerHeight()) {
var $sub = $scrollTo.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
if (!$sub.length) $sub = $scrollTo.find('input, select, textarea').filter(':visible').first();
if ($sub.length) $scrollTo = $sub;
headerHeight = $(window).outerHeight() / 4;
}
// 2) DOWN: include OPTIONAL questions again (unanswered below current)
if (!$scrollTo.length) {
// a) immediate next visible block if unanswered (required or optional)
if ($nextBlockEl.length && !isAnswered($nextBlockEl)) {
$scrollTo = $nextBlockEl;
} else {
// b) first unanswered field BELOW current (required OR optional)
for (var j = idx + 1; j < $visibleFields.length; j++) {
var $f2 = $visibleFields.eq(j);
if (!hasInputContainer($f2)) continue;
if (!isAnswered($f2)) { $scrollTo = $f2; break; }
}
// c) if none remain, go to the submit/footer
if (!$scrollTo.length) {
$scrollTo = $form.find('.gform_footer:visible').first();
if (!$scrollTo.length) $scrollTo = $('.gform_footer:visible').first(); // global fallback
headerHeight = computeHeaderHeight() + ($(window).outerHeight() / 4);
}
}
}
}
// No scroll on incorrect clicks
if ($scrollTo && $scrollTo.length) {
var elOffset = $scrollTo.is(':visible')
? $scrollTo.offset().top
: ($sourceBlockEl.offset().top + $sourceBlockEl.outerHeight());
$('html, body').animate({ scrollTop: elOffset - headerHeight }, 1000);
}
});
})(jQuery);
This keeps the **upward** correction strict (required-only) and restores **downward** progression to include optional questions so they’re not skipped.