Fixing GF scroll logic

User
$('.gfield_choice:not(:last) label').on('click', function() {
$(this).siblings('input').trigger('click'); // update GF conditional logic first

var scrollToEl = '';
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) && (isOverride || (typeof availableProducts !== 'undefined' && availableProducts.length > 0)));

if (isCorrectClick) {
// 1) Try going UP to nearest previous incorrect/error
var $incorrectFields = $('.gfield:visible').filter(function(){
var $f = $(this);
return $f.hasClass('gfield_error') ||
$f.hasClass('gquiz-field-incorrect') ||
$f.find('.gquiz-incorrect-choice:visible').length > 0 ||
$f.find('.choice_incorrect:visible').length > 0;
});

if ($incorrectFields.length) {
var srcTop = $sourceBlockEl.offset().top, $prevIncorrect = $();
$incorrectFields.each(function(){
var $f = $(this);
if ($f.offset().top < srcTop) $prevIncorrect = $f; // last one above = nearest previous
});
if ($prevIncorrect.length) {
scrollToEl = $prevIncorrect;

// mobile centering tweak for tall fields
if (scrollToEl.outerHeight() > $(window).outerHeight()) {
var $choice = scrollToEl.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
if ($choice.length) scrollToEl = $choice;
headerHeight = $(window).outerHeight() / 4;
}
}
}

// 2) If no incorrect above, fall back to normal DOWN auto-scroll
if (!scrollToEl) {
// next visible unanswered block (fix typo: .length)
if ($nextBlockEl.is(':visible')
&& $nextBlockEl.find('input:checked').length === 0
&& $nextBlockEl.find('.choice_selected').length === 0) {
scrollToEl = $nextBlockEl;
} else {
// first visible unanswered anywhere (required OR optional)
var found = false;
$('.gfield:visible').each(function(){
var $f = $(this);
if ($f.find('.ginput_container').length === 0) return; // skip HTML/section

var answered = false;
if ($f.find('.ginput_container_choice').length) {
answered = $f.find('input:radio:checked, input:checkbox:checked').length > 0;
} else {
answered =
$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
|| $f.find('select').filter(function(){ var v=$(this).val(); return v !== '' && v !== null; }).length > 0
|| $f.find('input[type="date"], input[type="time"]').filter(function(){ return $.trim($(this).val()) !== ''; }).length > 0;
}

if (!answered) { scrollToEl = $f; found = true; return false; }
});

if (!found) {
// nothing left unanswered → center submit
scrollToEl = $('.gform_footer');
headerHeight = headerHeight + ($(window).outerHeight() / 4);
}
}
}
}

// No scroll on incorrect clicks (per your requirement)
if (scrollToEl) {
var elOffset = $(scrollToEl).is(':visible')
? $(scrollToEl).offset().top
: $sourceBlockEl.offset().top + $sourceBlockEl.outerHeight();

$('html, body').animate({ scrollTop: elOffset – headerHeight }, 1000);
}
});
above is skipped non required question I want to fix this so that it also considere non required also persist current functioanlity

Assistant
Got it — two fixes:

1) don’t bind to “not(:last) label” (that skips the last choice);
2) treat selects with placeholder (e.g. `gf_placeholder`, disabled) as **unanswered** so optional questions aren’t skipped.

Drop-in replacement:

“`js
// Delegate so dynamically-shown fields also work
$(document).on('click', '.gfield_choice label', function () {
$(this).siblings('input').trigger('click'); // update GF conditional logic first

const $sourceBlockEl = $(this).closest('.gfield');
let scrollToEl = '';
let headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : $('#et-main-area').offset().top;

// helper: is a field answered?
function isFieldAnswered($f) {
// skip non-question blocks
if ($f.find('.ginput_container').length === 0) return true;

// choices
if ($f.find('.ginput_container_choice').length) {
return $f.find('input:radio:checked, input:checkbox:checked').length > 0;
}

// text-like inputs
const hasFilledText = $f
.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea, input[type="date"], input[type="time"]')
.filter(function () { return $.trim($(this).val() || '') !== ''; })
.length > 0;

// selects (placeholder should NOT count as answered)
const hasValidSelect = $f.find('select').filter(function () {
const $sel = $(this);
const $opt = $sel.find('option:selected');
const val = $sel.val();

// treat as unanswered if: empty/null, selected option is disabled, or has gf_placeholder
if (val === '' || val === null) return false;
if ($opt.is(':disabled') || $opt.hasClass('gf_placeholder')) return false;
return true;
}).length > 0;

// file inputs
const hasFile = $f.find('input[type="file"]').filter(function () { return this.files && this.files.length; }).length > 0;

return hasFilledText || hasValidSelect || hasFile;
}

// Only treat as "correct" if not flagged incorrect AND gating allows
const isCorrectClick = (
((($(this).attr('onclick') || '').indexOf('incorrect') === -1)) &&
(typeof isOverride !== 'undefined' && isOverride || (typeof availableProducts !== 'undefined' && availableProducts.length > 0))
);

if (isCorrectClick) {
// 1) Go UP to nearest previous incorrect/error (if any)
const $incorrectFields = $('.gfield:visible').filter(function () {
const $f = $(this);
return $f.hasClass('gfield_error')
|| $f.hasClass('gquiz-field-incorrect')
|| $f.find('.gquiz-incorrect-choice:visible').length > 0
|| $f.find('.choice_incorrect:visible').length > 0;
});

if ($incorrectFields.length) {
const srcTop = $sourceBlockEl.offset().top;
let $prevIncorrect = $();
$incorrectFields.each(function () {
const $f = $(this);
if ($f.offset().top < srcTop) $prevIncorrect = $f;
});
if ($prevIncorrect.length) {
scrollToEl = $prevIncorrect;

// mobile centering tweak for tall fields
if ($prevIncorrect.outerHeight() > $(window).outerHeight()) {
const $choice = $prevIncorrect.find('.gquiz-incorrect-choice:visible,

User
I want to change script so that it does not skip non required question
Assistant
Right now your script skips optional (non-required) questions because of this part:

// first visible unanswered anywhere (required OR optional)
var found = false;
$('.gfield:visible').each(function(){
  var $f = $(this);
  if ($f.find('.ginput_container').length === 0) return; // skip HTML/section

  var answered = false;
  ...
  if (!answered) { scrollToEl = $f; found = true; return false; }
});

The **problem** is in the logic for `$nextBlockEl` before that —
you only scroll down if **`$nextBlockEl` is visible AND unanswered**.
If it’s optional and already skipped by Gravity Forms’ required check, it gets bypassed.

### Minimal change to include non-required questions

Replace:

if ($nextBlockEl.is(':visible')
    && $nextBlockEl.find('input:checked').length === 0
    && $nextBlockEl.find('.choice_selected').length === 0) {
  scrollToEl = $nextBlockEl;
} else {

with:

if ($nextBlockEl.is(':visible')) {
  // always go to next visible block, regardless of required/answered
  scrollToEl = $nextBlockEl;
} else {

### Why this works
– The first branch will now always scroll to the next visible `.gfield` — even if it’s optional or already answered — keeping the natural order.
– If `$nextBlockEl` is hidden (conditional logic), it falls back to the full “find first visible unanswered” loop.

Do you want me to **rewrite the whole script** with this fix cleanly applied so that both the “go up to incorrect” and “scroll down” behaviours remain intact but no non-required question is skipped? That would avoid touching multiple places later.

User
I want to explain this again
given script skipped non required question and scroll to next required question event if in betwen there is non require question
SO I just want it simply should scroll to next question wheter it is reqired or non required
and also keep existing functionality
Assistant
Perfect—here’s a clean drop-in that keeps your current behavior (go UP to previous incorrect first; then existing fallbacks), but changes the DOWN step to **always go to the next visible question** (required or not).

Use delegated binding (works with GF’s dynamic DOM) and replace your handler with this:

$(document).on('click', '.gfield_choice label', function () {
  // Let GF update conditional logic first
  $(this).siblings('input').trigger('click');

  var $sourceBlockEl = $(this).closest('.gfield');
  var scrollToEl = '';
  var headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : $('#et-main-area').offset().top;

  // treat as "correct" only 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) );

  if (isCorrectClick) {
    // 1) Go UP to nearest previous incorrect/error
    var $incorrectFields = $('.gfield:visible').filter(function () {
      var $f = $(this);
      return $f.hasClass('gfield_error') ||
             $f.hasClass('gquiz-field-incorrect') ||
             $f.find('.gquiz-incorrect-choice:visible').length > 0 ||
             $f.find('.choice_incorrect:visible').length > 0;
    });

    if ($incorrectFields.length) {
      var srcTop = $sourceBlockEl.offset().top, $prevIncorrect = $();
      $incorrectFields.each(function () {
        var $f = $(this);
        if ($f.offset().top < srcTop) $prevIncorrect = $f;
      });
      if ($prevIncorrect.length) {
        scrollToEl = $prevIncorrect;

        // mobile centering tweak for tall fields
        if ($prevIncorrect.outerHeight() > $(window).outerHeight()) {
          var $choice = $prevIncorrect.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
          if ($choice.length) scrollToEl = $choice;
          headerHeight = $(window).outerHeight() / 4;
        }
      }
    }

    // 2) If no incorrect above, ALWAYS go to the next VISIBLE question
    if (!scrollToEl) {
      var $nextVisibleQuestion = $sourceBlockEl
        .nextAll('.gfield:visible')
        .filter(function () { return $(this).find('.ginput_container').length > 0; }) // skip sections/HTML blocks
        .first();

      if ($nextVisibleQuestion.length) {
        scrollToEl = $nextVisibleQuestion;
      } else {
        // 3) Fallbacks unchanged: first visible unanswered anywhere, else center submit
        var found = false;
        $('.gfield:visible').each(function () {
          var $f = $(this);
          if ($f.find('.ginput_container').length === 0) return; // skip non-question blocks

          var answered = false;
          if ($f.find('.ginput_container_choice').length) {
            answered = $f.find('input:radio:checked, input:checkbox:checked').length > 0;
          } else {
            answered =
              $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
              || $f.find('select').filter(function () { var v = $(this).val(); return v !== '' && v !== null; }).length > 0
              || $f.find('input[type="date"], input[type="time"]').filter(function () { return $.trim($(this).val()) !== ''; }).length > 0
              || $f.find('input[type="file"]').filter(function () { return this.files && this.files.length; }).length > 0;
          }

          if (!answered) { scrollToEl = $f; found = true; return false; }
        });

        if (!found) {
          scrollToEl = $('.gform_footer');
          headerHeight += ($(window).outerHeight() / 4);
        }
      }
    }
  }

  // No scroll on incorrect clicks
  if (scrollToEl) {
    var elOffset = $(scrollToEl).is(':visible')
      ? $(scrollToEl).offset().top
      : $sourceBlockEl.offset().top + $sourceBlockEl.outerHeight();

    $('html, body').animate({ scrollTop: elOffset - headerHeight }, 1000);
  }
});

What changed:
– The down-scroll step now picks `nextAll('.gfield:visible')` and **doesn’t care** if it’s required or optional—just the next visible question.
– Everything else (go up to previous incorrect; fallbacks to first unanswered / submit centering; no scroll on incorrect) stays intact.

User
but now

Add To Cart
this click stoped working on other page

Assistant
This isn’t your new scroll code — it’s a typo in the anchor.

You’ve got:

href="javascript:void(0}"

That closing brace `}` makes the browser try to run invalid JS on click, so your upsell handler never fires.

Fix it:

<a href="javascript:void(0)" class="upsell-addtocart" data-prodid="191361" style="text-decoration:none!important">
  <span class="upsell-cart" style="background:#acabab;padding:9px;color:white;border-radius:5px;text-transform:unset;font-weight:600;font-size:11px">
    Add To Cart
  </span>
</a>

Even better (more robust):

<a href="#" class="upsell-addtocart" data-prodid="191361" role="button">Add To Cart</a>
$(document).on('click', '.upsell-addtocart', function (e) {
  e.preventDefault();               // don’t navigate
  const id = $(this).data('prodid');
  // your add-to-cart logic here…
});

Optional safety so your GF script can’t affect other pages:

if ($('.gform_wrapper').length) {
  $(document).on('click', '.gfield_choice label', function(){ /* … */ });
}

After the fix, refresh and check the console—if you still see errors, paste them here and I’ll zero in.

User
Add To Cart
Assistant
That anchor is fine 👍 — now make sure the click handler isn’t being eaten by other scripts (smooth-scroll on `a[href="#"]`, etc.). Use a robust handler:

(function ($) {
  function onUpsellClick(e) {
    e.preventDefault();
    e.stopPropagation(); // avoid theme/global handlers grabbing it first

    const $a = $(this);
    const id = $a.data('prodid');
    // TODO: your add-to-cart logic here…
    // $.post('/cart/add', { product_id: id, qty: 1 }) ...
  }

  // bind for current + future elements
  $('.upsell-addtocart').off('click.upsell').on('click.upsell', onUpsellClick);
  $(document).off('click.upsell', '.upsell-addtocart')
             .on('click.upsell', '.upsell-addtocart', onUpsellClick);

  // keyboard accessibility
  $(document).on('keydown.upsell', '.upsell-addtocart', function (e) {
    if (e.key === 'Enter' || e.key === ' ') onUpsellClick.call(this, e);
  });
})(jQuery);

And tweak the HTML for a11y:

<a href="#" class="upsell-addtocart" data-prodid="191361" role="button" tabindex="0" style="text-decoration:none!important">
  <span class="upsell-cart" style="background:#acabab;padding:9px;color:#fff;border-radius:5px;text-transform:unset;font-weight:600;font-size:11px">Add To Cart</span>
</a>

If it still doesn’t fire, quick checks:
– Open DevTools → Console: any JS errors?
– Inspect the element: is there a parent with `pointer-events:none`?
– Search code for a global `$('a[href^="#"]').on('click', …)` that uses `stopImmediatePropagation()`. If found, keep the handler above and consider switching to `href="javascript:void(0)"` or add a class the global script excludes.

User
$(document).on('click', '.gfield_choice label', function () {
// Let GF update conditional logic first
$(this).siblings('input').trigger('click');

var $sourceBlockEl = $(this).closest('.gfield');
var scrollToEl = '';
var headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : $('#et-main-area').offset().top;

// treat as "correct" only 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) );

if (isCorrectClick) {
// 1) Go UP to nearest previous incorrect/error
var $incorrectFields = $('.gfield:visible').filter(function () {
var $f = $(this);
return $f.hasClass('gfield_error') ||
$f.hasClass('gquiz-field-incorrect') ||
$f.find('.gquiz-incorrect-choice:visible').length > 0 ||
$f.find('.choice_incorrect:visible').length > 0;
});

if ($incorrectFields.length) {
var srcTop = $sourceBlockEl.offset().top, $prevIncorrect = $();
$incorrectFields.each(function () {
var $f = $(this);
if ($f.offset().top < srcTop) $prevIncorrect = $f;
});
if ($prevIncorrect.length) {
scrollToEl = $prevIncorrect;

// mobile centering tweak for tall fields
if ($prevIncorrect.outerHeight() > $(window).outerHeight()) {
var $choice = $prevIncorrect.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
if ($choice.length) scrollToEl = $choice;
headerHeight = $(window).outerHeight() / 4;
}
}
}

// 2) If no incorrect above, ALWAYS go to the next VISIBLE question
if (!scrollToEl) {
var $nextVisibleQuestion = $sourceBlockEl
.nextAll('.gfield:visible')
.filter(function () { return $(this).find('.ginput_container').length > 0; }) // skip sections/HTML blocks
.first();

if ($nextVisibleQuestion.length) {
scrollToEl = $nextVisibleQuestion;
} else {
// 3) Fallbacks unchanged: first visible unanswered anywhere, else center submit
var found = false;
$('.gfield:visible').each(function () {
var $f = $(this);
if ($f.find('.ginput_container').length === 0) return; // skip non-question blocks

var answered = false;
if ($f.find('.ginput_container_choice').length) {
answered = $f.find('input:radio:checked, input:checkbox:checked').length > 0;
} else {
answered =
$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
|| $f.find('select').filter(function () { var v = $(this).val(); return v !== '' && v !== null; }).length > 0
|| $f.find('input[type="date"], input[type="time"]').filter(function () { return $.trim($(this).val()) !== ''; }).length > 0
|| $f.find('input[type="file"]').filter(function () { return this.files && this.files.length; }).length > 0;
}

if (!answered) { scrollToEl = $f; found = true; return false; }
});

if (!found) {
scrollToEl = $('.gform_footer');
headerHeight += ($(window).outerHeight() / 4);
}
}
}
}

// No scroll on incorrect clicks
if (scrollToEl) {
var elOffset = $(scrollToEl).is(':visible')
? $(scrollToEl).offset().top
: $sourceBlockEl.offset().top + $sourceBlockEl.outerHeight();

$('html, body').animate({ scrollTop: elOffset – headerHeight }, 1000);
}
});
after adding above script the problem start

Assistant
Yep—your GF handler is global (bound on `document`). On pages without the form, other scripts can still get tangled in delegation order. Scope it to the form wrapper and namespace the event so it **can’t** affect other pages.

Drop-in replacement (same logic, safer binding):

(function ($) {
  // Only bind if a Gravity Form exists on the page
  var $formWrap = $('.gform_wrapper');
  if (!$formWrap.length) return;

  // Bind on the form container (not document) + namespace
  $formWrap.off('click.gformAutoScroll', '.gfield_choice label')
           .on('click.gformAutoScroll', '.gfield_choice label', function () {

    // Let GF update conditional logic first
    $(this).siblings('input').trigger('click');

    var $sourceBlockEl = $(this).closest('.gfield');
    var scrollToEl = '';
    var headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : $('#et-main-area').offset().top;

    // treat as "correct" only 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));

    if (isCorrectClick) {
      // 1) Up to nearest previous incorrect/error
      var $incorrectFields = $('.gfield:visible').filter(function () {
        var $f = $(this);
        return $f.hasClass('gfield_error') ||
               $f.hasClass('gquiz-field-incorrect') ||
               $f.find('.gquiz-incorrect-choice:visible').length > 0 ||
               $f.find('.choice_incorrect:visible').length > 0;
      });

      if ($incorrectFields.length) {
        var srcTop = $sourceBlockEl.offset().top, $prevIncorrect = $();
        $incorrectFields.each(function () {
          var $f = $(this);
          if ($f.offset().top < srcTop) $prevIncorrect = $f;
        });
        if ($prevIncorrect.length) {
          scrollToEl = $prevIncorrect;

          // mobile centering tweak for tall fields
          if ($prevIncorrect.outerHeight() > $(window).outerHeight()) {
            var $choice = $prevIncorrect.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
            if ($choice.length) scrollToEl = $choice;
            headerHeight = $(window).outerHeight() / 4;
          }
        }
      }

      // 2) Else always go to the next visible question (required or not)
      if (!scrollToEl) {
        var $nextVisibleQuestion = $sourceBlockEl
          .nextAll('.gfield:visible')
          .filter(function () { return $(this).find('.ginput_container').length > 0; })
          .first();

        if ($nextVisibleQuestion.length) {
          scrollToEl = $nextVisibleQuestion;
        } else {
          // 3) Fallbacks unchanged
          var found = false;
          $('.gfield:visible').each(function () {
            var $f = $(this);
            if ($f.find('.ginput_container').length === 0) return; // skip non-question blocks

            var answered = false;
            if ($f.find('.ginput_container_choice').length) {
              answered = $f.find('input:radio:checked, input:checkbox:checked').length > 0;
            } else {
              answered =
                $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
                || $f.find('select').filter(function () { var v = $(this).val(); return v !== '' && v !== null; }).length > 0
                || $f.find('input[type="date"], input[type="time"]').filter(function () { return $.trim($(this).val()) !== ''; }).length > 0
                || $f.find('input[type="file"]').filter(function () { return this.files && this.files.length; }).length > 0;
            }

            if (!answered) { scrollToEl = $f; found = true; return false; }
          });

          if (!found) {
            scrollToEl = $('.gform_footer');
            headerHeight += ($(window).outerHeight() / 4);
          }
        }
      }
    }

    // No scroll on incorrect clicks
    if (scrollToEl) {
      var elOffset = $(scrollToEl).is(':visible')
        ? $(scrollToEl).offset().top
        : $sourceBlockEl.offset().top + $sourceBlockEl.outerHeight();

      $('html, body').animate({ scrollTop: elOffset - headerHeight }, 1000);
    }
  });
})(jQuery);

Also, keep your upsell button bulletproof (in case other global handlers exist):

$(document).on('click', '.upsell-addtocart', function (e) {
  e.preventDefault();
  e.stopPropagation();
  // add-to-cart logic...
});

Scoping the GF listener to `.gform_wrapper` removes any chance it interferes with other pages/components.

User
not working
Assistant
Gotcha—the scroll script is probably throwing an error on pages **without** that Divi header element, which then kills all JS (so your upsell click never binds).

Main fixes:
– **Null-safe headerHeight** (don’t read `.offset().top` if `#et-main-area` isn’t there).
– **Scope to the specific form** that actually has choice labels (so other pages aren’t touched).
– **Limit all lookups** to that form scope.

Drop-in replacement:

(function ($) {
  // Find only forms that actually have choice labels
  var $scope = $('.gform_wrapper').filter(function () {
    return $(this).find('.gfield_choice label').length > 0;
  }).first();

  if (!$scope.length) return; // no quiz/choice form on this page

  // bind inside the form scope + namespace
  $scope.off('click.gformAutoScroll', '.gfield_choice label')
        .on('click.gformAutoScroll', '.gfield_choice label', function () {

    // Let GF update conditional logic first
    $(this).siblings('input').trigger('click');

    var $sourceBlockEl = $(this).closest('.gfield');
    var scrollToEl = '';

    // NULL-SAFE header height
    var $mainArea = $('#et-main-area');
    var headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : ($mainArea.length ? $mainArea.offset().top : 0);

    // Safe gating (vars may not exist on all pages)
    var isCorrectClick =
      ((($(this).attr('onclick') || '').indexOf('incorrect') === -1)) &&
      ((typeof isOverride !== 'undefined' && isOverride) ||
       (typeof availableProducts !== 'undefined' && availableProducts.length > 0));

    if (isCorrectClick) {
      // 1) Up to nearest previous incorrect/error (within this form only)
      var $incorrectFields = $scope.find('.gfield:visible').filter(function () {
        var $f = $(this);
        return $f.hasClass('gfield_error') ||
               $f.hasClass('gquiz-field-incorrect') ||
               $f.find('.gquiz-incorrect-choice:visible').length > 0 ||
               $f.find('.choice_incorrect:visible').length > 0;
      });

      if ($incorrectFields.length) {
        var srcTop = $sourceBlockEl.offset().top, $prevIncorrect = $();
        $incorrectFields.each(function () {
          var $f = $(this);
          if ($f.offset().top < srcTop) $prevIncorrect = $f;
        });
        if ($prevIncorrect.length) {
          scrollToEl = $prevIncorrect;

          // mobile centering tweak for tall fields
          if ($prevIncorrect.outerHeight() > $(window).outerHeight()) {
            var $choice = $prevIncorrect.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
            if ($choice.length) scrollToEl = $choice;
            headerHeight = $(window).outerHeight() / 4;
          }
        }
      }

      // 2) Else always go to the next visible question (required or not) within this form
      if (!scrollToEl) {
        var $nextVisibleQuestion = $sourceBlockEl
          .nextAll('.gfield:visible')
          .filter(function () { return $(this).find('.ginput_container').length > 0; })
          .first();

        if ($nextVisibleQuestion.length) {
          scrollToEl = $nextVisibleQuestion;
        } else {
          // 3) Fallbacks unchanged but scoped
          var found = false;
          $scope.find('.gfield:visible').each(function () {
            var $f = $(this);
            if ($f.find('.ginput_container').length === 0) return; // skip non-question blocks

            var answered = false;
            if ($f.find('.ginput_container_choice').length) {
              answered = $f.find('input:radio:checked, input:checkbox:checked').length > 0;
            } else {
              answered =
                $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
                || $f.find('select').filter(function () { var v = $(this).val(); return v !== '' && v !== null; }).length > 0
                || $f.find('input[type="date"], input[type="time"]').filter(function () { return $.trim($(this).val()) !== ''; }).length > 0
                || $f.find('input[type="file"]').filter(function () { return this.files && this.files.length; }).length > 0;
            }

            if (!answered) { scrollToEl = $f; found = true; return false; }
          });

          if (!found) {
            scrollToEl = $scope.find('.gform_footer');
            headerHeight += ($(window).outerHeight() / 4);
          }
        }
      }
    }

    // No scroll on incorrect clicks
    if (scrollToEl && $(scrollToEl).length) {
      var $target = $(scrollToEl);
      var elOffset = $target.is(':visible')
        ? $target.offset().top
        : $sourceBlockEl.offset().top + $sourceBlockEl.outerHeight();

      $('html, body').animate({ scrollTop: elOffset - headerHeight }, 1000);
    }
  });
})(jQuery);

And keep your upsell click bullet-proof:

$(document).off('click.upsell', '.upsell-addtocart')
           .on('click.upsell', '.upsell-addtocart', function (e) {
  e.preventDefault();
  e.stopPropagation();
  const id = $(this).data('prodid');
  // add-to-cart logic...
});

This prevents the GF script from running (or erroring) on pages without that quiz form and stops it from breaking your upsell button.

Leave a Comment

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