OK I figured out a pretty good way to do this, for future reference.
First, copy checkout.js
from the WooCommerce javascript folder to your theme's js folder.
Then add this to your theme's functions.php
, deregistering the default script and replacing it with your custom version:
add_action( 'wp_enqueue_scripts', 'kg_replace_wc_scripts', 99 );
function kg_replace_wc_scripts(){
if( is_checkout() ){
$path = get_template_directory_uri() . '/js/checkout.js';
$time = filemtime($path);
wp_deregister_script('wc-checkout');
wp_register_script('wc-checkout', $path,
array( 'jquery', 'woocommerce', 'wc-country-select', 'wc-address-i18n' ), $time, TRUE);
wp_enqueue_script('wc-checkout');
}
}
(Optionally use the WC version as a version string, but I like to use filemtime
so every time the file is modified it updates on the live site without needing a cache clear)
Then in your new checkout.js
, comment out or remove this line (line 35 in this version):
this.$checkout_form.on( 'input blur change', '.input-text, select, input:checkbox', this.validate_field );
This will give the user a break and keep the fields from validating before they're done filling things out.
Now you need to add a new function to wc_checkout_form
to handle the validation of all fields at once, here's mine:
validate_all_fields: function() {
var any_invalid = false;
var ship_to_diff = $('#ship-to-different-address input').is(':checked');
$('.kg-invalid-msg').fadeOut(500, function() {
$(this).remove();
});
$('.woocommerce-invalid').removeClass('woocommerce-invalid woocommerce-invalid-required-field');
$('.validate-required').each(function() {
var $this = $(this).find('input[type=checkbox],select,.input-text'),
$parent = $this.closest( '.form-row' ),
validated = true,
validate_required = $parent.is( '.validate-required' ),
validate_email = $parent.is( '.validate-email' );
if (!ship_to_diff && $this.parents('.woocommerce-shipping-fields').length) {
return true;
}
if ( validate_required ) {
if ( 'checkbox' === $this.attr( 'type' ) && ! $this.is( ':checked' ) ) {
$parent.removeClass( 'woocommerce-validated' ).addClass( 'woocommerce-invalid woocommerce-invalid-required-field' );
validated = false;
any_invalid = true;
} else if ( $this.val() === '' ) {
$parent.removeClass( 'woocommerce-validated' ).addClass( 'woocommerce-invalid woocommerce-invalid-required-field' );
validated = false;
any_invalid = true;
}
}
if ( validate_email ) {
if ( $this.val() ) {
/* https://stackoverflow.com/questions/2855865/jquery-validate-e-mail-address-regex */
var pattern = new RegExp(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i);
if ( ! pattern.test( $this.val() ) ) {
$parent.removeClass( 'woocommerce-validated' ).addClass( 'woocommerce-invalid woocommerce-invalid-email' );
validated = false;
any_invalid = true;
}
}
}
if ( validated ) {
$parent.removeClass( 'woocommerce-invalid woocommerce-invalid-required-field woocommerce-invalid-email' ).addClass( 'woocommerce-validated' );
}
});
if (any_invalid) {
// Scroll to first invalid input
var $first_invalid = $('.woocommerce-invalid:first');
var $msg = $('<div class="kg-invalid-msg">Please check your info</div>').appendTo($first_invalid);
$('html,body').animate({
scrollTop: $first_invalid.offset().top - 70
}, 1000);
$first_invalid.find('input,select').on('input change', function() {
$msg.fadeOut(500, function() {
$msg.remove();
});
});
$('.woocommerce-invalid').find('input,select').on('input change', function() {
$(this).closest('.form-row').removeClass('woocommerce-invalid woocommerce-invalid-required-field');
});
}
}
This will scroll to the first invalid input (instead of all the way to the top) when the form fails to validate, and add a note to the first invalid input while coloring ALL the invalid inputs red. You can modify this pretty easily to get it working how you want.
Finally you just have to trigger this function on submit by adding this line to the submit
function of wc_checkout_form
:
wc_checkout_form.validate_all_fields();
Now the checkout fields will leave you alone until you click "Place Order", after which the validation will kick in.