Update field validation

User
$('.gfield_choice:not(:last) label').on('click', function () {
$(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

Assistant
Got it — just tighten the UP-scan so it only targets **required** fields.

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; }
});
User
What is your ethnicity?

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

Assistant
You’re seeing that because your **fallback scan** searches the *entire* form from the top, and it also includes optional fields—so it can jump “up” to the first unanswered optional (like your ethnicity field).

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))`.)

User
give me full code
Assistant
Here’s the full script with **required-only** UP scan and **required-only** DOWN fallback (scoped to the current form and safe against duplicate bindings):

(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);
User
but it now skip non required question when sroll down
Assistant
Yep — keep the **UP** scan required-only, but let the **DOWN** scroll include optional fields again. Drop-in script:

(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.

Leave a Comment

Your email address will not be published. Required fields are marked *