1

By default, Woocommerce validates fields on the checkout page as soon as they're blurred. So if someone clicks into a field by mistake and then clicks out, the field shows as "invalid" even though nothing has been submitted. We find this kind of annoying and potentially stressful for users.

What we'd like to do is change the validation so it will only kick in when the "Place Order" button is clicked (or the form is submitted in general). However, we can't just remove checkout.min.js altogether, because there are other scripts unrelated to field validation that we likely want to keep.

I've tried this hacky method to remove the default validation: jQuery(document.body).on('init_checkout', function (event) { setTimeout(function () { jQuery('.validate-required').removeClass('validate-required woocommerce-invalid woocommerce-invalid-required-field'); }, 1000); });

While this keeps the fields from turning red when blurred, the page still scrolls to the top and displays an error when "Place Order" is clicked, so there must be something else to the validation. The error up top only refers to the credit card fields, though...we are using the Woocommerce Authorize.NET AIM plugin so that could have something to do with it.

Has anybody done something similar to this and had any luck?

Keith Pickering
  • 696
  • 10
  • 24

2 Answers2

2

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.

Keith Pickering
  • 696
  • 10
  • 24
  • 1
    `get_template_directory_uri` only returns the path to the parent theme, which is the wrong path in case you added the script to your child theme folder. Changed the function call to `get_stylesheet_directory_uri()` to get the active (child) theme path. Credits to @tim-malone https://wordpress.stackexchange.com/questions/230085/get-template-directory-uri-pointing-to-parent-theme-not-child – Laszlo Schürg Aug 24 '18 at 14:39
0

For anyone else having the same problem, I solved it like this without editing the checkout.js:

Hide errors until showErrors class is present:

.woocommerce form .form-row.woocommerce-invalid:not(.showErrors) .select2-container, 
.woocommerce form .form-row.woocommerce-invalid:not(.showErrors) input.input-text, 
.woocommerce form .form-row.woocommerce-invalid:not(.showErrors) select {
    border: 1px solid #FFF !important;
}

Then add showErrors on checkout error:

$(document.body).on('checkout_error', function () {
    $('.woocommerce-invalid-required-field').addClass('showErrors');
  });
Rasmus B
  • 41
  • 1