Form validation fix

User
<script>
jQuery(document).ready(function ($) {

      //Check for an entry cookie in the product's category
      <?php
      $product_cat = false;
      $product_id = $product->get_id();
      $product_cats = get_the_terms( $product->get_id(), 'product_cat' );
      if ( $product_cats ) {
        $product_cat = $product_cats[0]->term_id;
      }
      if ( $product_cat && !get_post_meta( $product_id, '_pmm_consultation_form_id', true ) ): ?>
        var categoryId = '<?php echo absint($product_cat); ?>';
        if ( Cookies.get('entry_category-' + categoryId) || ( document.cookie.indexOf('wp_user_logged_in') !== -1 && Cookies.get('logged_in_entry_category-' + categoryId) ) ) {
          $('.single_add_to_cart_button').text('Select your product');
        }
      <?php endif; ?>

      //Disable enter-to-submit on quantity input (stops adding to cart without going through consultation first)
      $('input[name=quantity]').on('keypress', function(){
        if (event.which == '13') {
          event.preventDefault();
          return false;
        }
      });

      //Custom add to cart button that requires consultation form
      $('#consult-first').on( 'click', function(e) {
         e.preventDefault();
         e.stopImmediatePropagation();

         //check that the button isn't disabled, and that we have a variation selected (this would be false in a re/loading state which sometimes happens on longer variation product loads)
         if ( !$(this).hasClass("disabled") && $('input[name="variation_id"]').val() > 0 ) {
            $(this).parents('form').submit();
        }
        //if someone clicks a disabled/loading button don't do anything
       return false;
      });

      if ( $('#form_id_override').length > 0 ) {
        $('.single_add_to_cart_button').on( 'click', function(event){
          event.preventDefault();
          event.stopImmediatePropagation();

          $(this).attr('name', '');
          $(this).parents('form').submit();
        });
      }

    });
</script>
<script>
jQuery(document).ready(function ($) {
          //Make sure we scroll to the top for the consultation entry display
          setTimeout(function(){
            if ( $('.returning').is(':visible') ) {
              window.onunload = function(){ window.scrollTo(0,0); }
            }
          },1);


          $('.reveal-consultation').on('click', function(event){
            event.preventDefault();
            $('.consultation-container').fadeIn();
            $('.changed-condition').fadeIn();
            $('.condition-choice').slideUp();
            return false;
          });

            //validate Gform
            var form = $('.gform_wrapper > form');
            $('.entry-content').on("click", "input[type=radio]", function (e) {

                if ( $('#product_id').val() != undefined ) {
                    //If any incorrect choices are visible it means the form is invalid so disable the submit button
                    if ( $('.choice_incorrect').is(':visible') == true ) {
                        $('.gform_button').addClass("disabled");
                    } else {
                        $('.gform_button').removeClass("disabled");
                    }
                }
            });

            /**
             * Show error message
             * @param {string} msg
             * @returns {void}
             */
            function showError(msg) {
              $('.validation_error').empty();
	            $('.validation_error').css({borderTop: "none", borderBottom: "none", padding: 0});
              $('.gform_heading').append('<div class="validation_error">' + msg + '</div>');
            }

			<?php
			//Set up a var that allows us to handle things differently if this is a pingdom bot
			$is_pingdom = isset( $_SERVER['HTTP_USER_AGENT'] ) ? stripos( $_SERVER['HTTP_USER_AGENT'], 'pingdom' ) : false;
			$variation_id = isset( $_POST['variation_id'] ) ? intval($_POST['variation_id']) : ''; ?>

        //Form submission
        form.find('input[type=submit]').click(function (e) {
            e.preventDefault();

            if (!formFilled()) {
                showError('Please fill all (*) required fields.');
                $('html, body').animate({scrollTop: form.offset().top - 100}, 'slow');
            } else if (!hasIncorrects()) {
					<?php
					//Redirect straight to add-to-cart without generating an entry if this is pingdom
					if ( $is_pingdom !== false) { ?>
						window.location = "<?php echo site_url("/cart/?add-to-cart=" . $variation_id ); ?>";
					<?php } else {

            //If a GET request, replace the history with GET params so we can keep our nice clean POST URL
            if ( !( isset($_GET['cat']) || isset($_GET['product_id']) ) || isset( $_POST['form_id_override'] ) ) {
              $url = add_query_arg( pmm_get_product_data('select_treatment') ); ?>
              history.replaceState(null, "Consultation Form: Submitted", "<?php echo esc_url_raw($url); ?>");
            <?php } ?>
            $('.gform_footer').addClass('loading');
						form.submit();
					<?php } ?>
                } else {
                    $('.gform_heading .validation_error').hide();
                }
            });

            //check each radio has checked
            function formFilled() {
                checked = true;
                form.find('.gfield_contains_required:visible').each(function () {
                    if ( $(this).find('.ginput_container_choice input:radio:checked').length === 0 ) {
                        //Check for selected choice - when Gravity Forms switches conditional fields it resets the checked prop
                        var selectedChoice = $(this).find('.choice_selected');
                        if ( selectedChoice.length > 0 ) {
                            selectedChoice.siblings('input[type="radio"]').prop('checked', true);
                        } else {
                          checked = false;
                        }
                    }
                });
                return checked;
            }

      			//Ensure that when a label is clicked, the associated checkbox is checked
      			//e.g. if a user selects text inside the label
      			$('.ginput_container_choice label').on('click', function(){
      				var radioEl = $(this).siblings('input[type="radio"]');
      				var isChecked = radioEl.prop('checked');
      				if (!isChecked) {
      					radioEl.prop('checked', true);
      				}
      			});

            /**
             * Exclusions logic
             */

            var availableProductsEl = $('#available_products');
            var isOverride = $('#add-to-cart').length > 0;
            if ( !isOverride ) {
              //Store a master copy of the available products list, so we can restore exclusions when customers switch choices
              var availableProductsMaster = availableProductsEl.length && $('#available_products').val().split(',');
              var availableProducts = availableProductsEl.length && $('#available_products').val().split(',');

              //Run a full check and applying of exclusions of all selected options on every new selection
              $('.gfield_choice label').on('click', function() {

                  //Collect (unique) exclusions from all checked options
                  var exclusions = [];

                  $('.gfield_choice input:checked').each(function() {
                      var choiceExclusions =  $(this).parent().data('exclusions');

                      if ( choiceExclusions ) {
                        for (i = 0; i < choiceExclusions.length; ++i ) {
                          if ( exclusions.indexOf(choiceExclusions[i]) === -1 ) {
                            exclusions.push(choiceExclusions[i]);
                          }
                        }
                      }
                  })

                  //Search for the exclusions in the availableProductsMaster and return as a new array
                  availableProducts = $.grep(availableProductsMaster, function(value) {
                      return $.inArray(value, exclusions) < 0;
                  });

                  //Update the hidden input element
                  availableProductsEl.val(availableProducts.join(','));

                  //Handle no products being left in the available list
                  //Disable the submit button and display the error modal
                  if (availableProducts.length === 0) {
                    var noProductsMessage = '<div class="choice_incorrect choice_no_products">Sorry based on your answers there are no suitable treatments we recommend for you. Please contact us on 0208 894 6080 to discuss this.</div>';

                    if ( $(this).parents('.gfield').find('.choice_no_products').length === 0 ) {
                      $(this).parents('.gfield').children('.choice_left').append(noProductsMessage);
                    } else {
                      $(this).parents('.gfield').find('.choice_no_products').show();
                    }

                    $('.gform_button').addClass("disabled");
                  } else {
                    $('.choice_no_products').hide();
                    $('.gform_button').removeClass("disabled");
                  }
              });
            }

            //check has a incorrect answer
            function hasIncorrects() {
                var goTo = false;
                $.each(form.find('.choice_incorrect:visible'), function (i) {
        						//figure out if we need to offset the scrollTo position (for non-mobile)
        						var headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : $('#et-main-area').offset().top;

        						//Set target element based on element height vs window height (stops very long questions appearing wrongly on mobile)
        						if ( $(this).parent().parent().outerHeight() > $(window).outerHeight() ) {
        							goTo = $(this);
        							headerHeight = $(window).outerHeight() / 4;
        						} else {
        							goTo = $(this).parent().parent();
        						}

                    $('html, body').animate({
                        scrollTop: $(goTo).offset().top - headerHeight
                    }, 1000);
                    return false;
                });
              return goTo ? true : false
            }

			<?php
			//Don't do autoscroll if this is a pingdom bot
			if ( $is_pingdom === false ) { ?>

				//Auto-scroll to the next question if a correct answer was selected (except for the last question)
        $('.gfield_choice:not(:last) label').on('click', function () {
    $(this).siblings('input').trigger('click');

    if ($(this).attr('onclick')?.indexOf('incorrect') === -1 && (isOverride || availableProducts.length > 0)) {
        var sourceBlockEl = $(this).closest('.gfield');
        var nextBlockEl = sourceBlockEl.nextAll('.gfield:visible').first();

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

        let scrollToEl = nextBlockEl.length ? nextBlockEl : $('.gform_footer');
        if (!nextBlockEl.length) {
            headerHeight += $(window).outerHeight() / 4;
        }

        var elOffset = scrollToEl.offset().top;
        $('html, body').animate({ scrollTop: elOffset - headerHeight }, 1000);
    }
});
			<?php } ?>
        });
</script>
<script>
jQuery(document).ready(function($){

        //Proceed to checkout bar - sticky on scroll
        if ( $('.treatment-continue').length > 0 ) {
          var $treatmentContinue = $('.treatment-continue');
          var elPos = $treatmentContinue.offset();
          $(window).on('scroll', function() {
            if ( $(window).scrollTop() > elPos.top ) {
              $treatmentContinue.addClass('sticky');
            } else {
              $treatmentContinue.removeClass('sticky');
            }
          }).trigger('scroll');
        };

        //Disable input on selected products
        $('.selected select, .selected .qty').prop('disabled','disabled');

        function selectTreatment(e) {
           var $selectButton = $(e.target);

          //check that the button isn't disabled, and that we have a variation selected (this would be false in a re/loading state which sometimes happens on longer variation product loads)
          if ( !$selectButton.hasClass("disabled") && $selectButton.siblings('input[name="variation_id"]').val() > 0 ) {
            var form = $selectButton.parents('form');
            form.submit();

            $selectButton.removeClass('added');
            $selectButton.addClass('loading');

          }

          //if someone clicks a disabled/loading button don't do anything
          return false;
        }

        function deselectTreatment( $removeButton ) {
          var cartItemKey = $removeButton.data('remove_item');
          $removeButton.removeClass('can-update').removeClass('deselect').removeClass('added').addClass('loading');
          $.post( '/?wc-ajax=remove_from_cart', 'cart_item_key=' + cartItemKey, function(response){
            if ( ! response || ! response.fragments ) {
              window.location.reload(true);
              return;
            }

            //Reset parent element
            $removeButton.parents('.product').removeClass('selected');
            $removeButton.parents('.product').find('select, .qty').prop('disabled', false);

            //Reset button
            $removeButton.removeClass('loading').addClass('added').removeClass('deselect');
            $removeButton.text('Removed');

            //Hide continue section if we've deselected the last item
            if ( $('.deselect').length === 0 ) {
              $('.treatment-continue').slideUp();
            }

            setTimeout(function(){
              $removeButton.text('Select').removeClass('added');
              $( document.body ).trigger( 'removed_from_cart', [ response.fragments, response.cart_hash, $removeButton ] );
              $removeButton.on('click', function(e){ selectTreatment(e) });
              $('.select_treatment.deselect').one( 'click', function(){
                deselectTreatment( $(this) );
              });
            }, 1500);
          } );
          return false;
        }


        $('.select_treatment').on('click', function(event){
          event.preventDefault();
          event.stopImmediatePropagation();

          if ( $(this).hasClass('deselect') ) {
            deselectTreatment( $(this) );
          } else {
            //Custom AJAX submission of the "Select" button
            selectTreatment(event);
          }

        });

        $('.view-more').on( 'click', function(){
          $(this).toggleClass('opened');
          $(this).siblings('.woocommerce-product-details__short-description').slideToggle();
        });

      });
</script>

Consultation Form

Select a Consultation Form…

<script>
jQuery(document).ready(function($){
          $('body').on('show_variation', function(){
              $(this).find('button.single_add_to_cart_button').removeClass('disabled wc-variation-is-unavailable');
          });
        });
</script>
<script>
jQuery("#forceResumbission").on("change", function(){
    if ( jQuery(this).is(":checked") ) {
        form.forceResumbission = true;
    } else {
        form.forceResumbission = null;
    }
  });
</script>

';
return $button_pre . $button;
}

//Set the force update time using PHP functions for consistency
add_filter( 'gform_form_update_meta', 'pmm_force_update_meta_save' );

function pmm_force_update_meta_save( $meta ) {
if ( isset( $meta['forceResumbission'] ) && $meta['forceResumbission'] == true ) {
$now = new DateTime();
$meta['forceResumbission'] = $now->format('U');
}
return $meta;
}

/**
* Check if an entry needs to be submitted again
* This would be either due to being from the previous plugin, or because of the form being updated with "force resubmission" ticked
*/

function pmm_entry_needs_resubmitting( $entry_id, $form_id, $product_id ) {

$force_resubmission = false;

//Check for the available_products meta – if it's not there this must be an entry from the previous plugin
//Also check if a consultation product level override is in place, then we skip this check
if ( !gform_get_meta( $entry_id, 'available_products' ) && !get_post_meta( $product_id, '_pmm_consultation_form_id', true ) ) {
//echo 'DEBUG: old form';
$force_resubmission = true;
} else {

//Check form's meta for a force resubmit
$form = GFAPI::get_form($form_id);
$entry = GFAPI::get_entry($entry_id);
if ( $form && !is_wp_error( $entry ) ) {

//If the entry's form id doesn't match the passed form id, it's automatically invalid
if ( absint($form_id) !== absint($entry['form_id']) ) {
//echo 'DEBUG: entry's form id does not match passed form id';
return true;
}

if ( isset($form['forceResumbission']) ) {

$force_time = DateTime::createFromFormat( 'U', $form['forceResumbission'] );
$entry_time = DateTime::createFromFormat( 'Y-m-d H:i:s', $entry['date_created'] );

//If forceResubmission date is greater (newer) than the entry submission time we need to force a new entry
if ( $force_time > $entry_time ) {
//echo 'DEBUG: form updated with force resubmit entry (ID:' . $entry_id . ') ' . $entry_time->format('Y-m-d H:i:s') . ' vs. ' . $force_time->format('Y-m-d H:i:s');
$force_resubmission = true;
}
}
}
}
return $force_resubmission;
}
formFilled function not work for other required field like text box even if text box filled it alywas return false

Assistant
You're correct — the `formFilled()` function in your JS validation script is only checking for **required radio buttons**, not for **other required fields** like text inputs, textareas, checkboxes, dropdowns, etc. That’s why it's returning `false` even when other required fields are filled.

### ✅ Here's what's wrong:

form.find('.gfield_contains_required:visible').each(function () {
    if ( $(this).find('.ginput_container_choice input:radio:checked').length === 0 ) {
        ...
        checked = false;
    }
});

This only targets **radio inputs**, and skips:
– Text inputs
– Textareas
– Dropdowns (selects)
– Checkboxes

### ✅ Suggested Fix (Improved `formFilled()` function):

Replace your current `formFilled()` function with this version:

function formFilled() {
    let isValid = true;

    form.find('.gfield_contains_required:visible').each(function () {
        const field = $(this);

        // Skip hidden fields
        if (!field.is(':visible')) return;

        // Get the actual input element(s)
        const input = field.find('input, select, textarea').first();

        // If it's radio or checkbox, check if any are selected
        if (input.is('[type=radio]') || input.is('[type=checkbox]')) {
            if (field.find('input:checked').length === 0) {
                isValid = false;
                return false; // Break out of .each loop
            }
        } else {
            // For text, textarea, select — check value
            if (!input.val() || input.val().trim() === '') {
                isValid = false;
                return false;
            }
        }
    });

    return isValid;
}

### ✅ Benefits of this fix:
– It works for **all required field types**.
– It checks if the field is **visible**.
– It handles both **single and multi-input fields** (radio/checkbox groups).
– It avoids false negatives on filled fields.

### ✅ Optional Enhancement:
If you're using Gravity Forms with conditional logic or multi-input fields, you may want to adjust the selectors or add specific Gravity Forms API calls for advanced field types (like name, address, etc.).

Let me know if you want the revised version inserted into the full `pmm_inject_gforms_js` script block — or if you’d like scroll-to-error logic enhanced too.

Leave a Comment

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