Form validation fix
August 7, 2025 · AI Conversations
<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
—
### ✅ 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.