Support

Account

Home Forums Front-end Issues Sending user to Tab on validation errors

Solving

Sending user to Tab on validation errors

  • I found a missing feature this time, when you use Tabs to organize a large form, when validating, the message is too poor to indicate an error in a field behind a hidden Tab. I found a solution. I don’t know if it will work for every situation, but I think so!

    
    acf.add_filter('validation_complete', function( json, $form ){
    	if(json.errors) {
    		var field = jQuery('[name="' + json.errors[0].input + '"]', $form).parents('.acf-field');
    			field = field[field.length - 1];
    		var tab = jQuery(field, $form).prev('.acf-field-tab').attr('data-key');
    		jQuery('.acf-tab-wrap a[data-key=' + tab + ']', $form).click();
    	}
    	return json;
    });
    
  • Nice solution, I’m sure that will come in handy for others.

  • Thanks @edir worked like a charm!

  • I’ve expanded on this to just highlight the field group and the tab in which fields below have errors. I haven’t tested this code extensively but it should work with all of the simple fields (everything except repeater, flexible content, etc). Those may work as well but I haven’t tested them.

    This is sort of the approach I’ve been needing when I start getting a lot of fields and a lot of tabs on a page template.

    
    (function($) {
    
    	$(function() {
    
    		var TabErrorMarker = {
    			errorClass: 'acf-tab-validation-errors',
    			errors: [],
    			fields: [],
    			init: function($form) {
    				var self = this;
    				this.$form = $form;
    				this.$tabs = this.$form.find('.acf-tab-wrap a');
    
    				var $groups = this.$form.find('.acf-postbox');
    
    				// Find all Field groups in form and then find all tabs in form.
    				// Then build a tabs array where each tab keeps track of its parent field group as well as its child fields.
    
    				$groups.each(function(gIndex) {
    					var $group = $(this),
    					gKey = $group.attr('id').replace('acf-', ''),
    					$tabs = $group.find('.acf-tab-wrap a');
    
    					$tabs.each(function(index) {
    						self.addTabObject($(this), {
    							index: $group.index(),
    							$el: $group
    						});
    					});
    				});
    
    				// ACF Hooks for adding/removing error classes on tabs and field groups of error fields hidden behind tabs.
    
    				acf.add_filter('validation_complete', this.proxy(this.validateTabs));
    
    				acf.add_action('validation_begin', this.proxy(this.removeErrors));
    
    				acf.add_action('invalid_field', this.proxy(this.addTabError));
    				return this;
    			},
    			addTabObject($tabLink, group) {
    				// Build the tab object before handing it off to the 'this.fields' array.
    				var self = this,
    				key = $tabLink.attr('data-key'),
    				$label = group.$el.find('.acf-field-tab').filter('[data-key="' + key + '"]'),
    				$fields = $label.nextUntil('.acf-field-tab'),
    
    				tab = {
    					index: $tabLink.index(),
    					key: key,
    					$link: $tabLink,
    					$label: $label
    				};
    
    				$fields.each(function(fIndex) {
    					self.addFieldObject($(this), tab, group);
    				});
    
    				return tab;
    			},
    			addFieldObject($field, tab, group) {
    				var key = $field.attr('data-key'),
    				type = $field.attr('data-type'),
    				$input = $field.find('#acf-' + key),
    				field = {
    					index: $field.index(),
    					key: key,
    					type: type,
    					$input: $input,
    					tab: tab,
    					group: group
    				};
    
    				this.fields.push(field);
    				return field;
    			},
    			validateTabs: function(json, $form){
    
    				// Check for validation errors and begin looping through all tabs in the form if any.
    				
    				this.errors = (json.errors || []);
    				if(this.errors.length) {
    					this.addErrors();
    				} else {
    					this.removeErrors();
    				}
    				return json;
    			},
    			addErrors: function() {
    
    				// Loop over each error, find the field related to the error and mark the parent tab with an error class.
    						
    				this.errors.forEach(this.proxy(function(err, index) {
    					var key = this.errors[index].input.replace('acf[', '').replace(']', ''),
    					$field = this.$form.find('.acf-field[data-key="' + key + '"]');
    
    					this.addTabError($field, this.$form);
    				}));
    				return this;
    			},
    			removeErrors: function () {
    
    				// Loop over each tab and check if all fields below that tab are error free.
    				// If so, remove the error class from the tab.
    						
    				var self = this;
    
    				this.$tabs.each(function(i) {
    					self.removeTabError($(this));
    				});
    				return this;
    			},
    			addTabError: function($field) {
    
    				// Add the error class to the tab and field group of any field that has validation errors
    
    				var index = $field.index();
    				key = $field.attr('data-key'),
    				$input = $field.find('[id="acf-' + key + '"]'),
    				$tabLabels = $($field.get(0), this.$form).prevAll('.acf-field-tab'),
    				tabKey = $tabLabels.eq(0).attr('data-key'),
    				$tab = $('.acf-tab-wrap a[data-key=' + tabKey + ']', this.$form),
    				$group = $tab.closest('.acf-postbox');
    
    				$tab.addClass(this.errorClass);
    				$group.addClass(this.errorClass);
    				return this;
    			},
    			removeTabError: function($tabLink) {
    
    				// Remove the error class to the tab and field group of any field that has validation errors
    				
    				var self = this,
    				$tabLinks = $tabLink.parents('ul').find('li a');
    
    				// Loop over each tab link, find all fields with an error class 
    				$tabLinks.each(function(i) {
    					var $tab = $(this),
    					$group = $tab.closest('.acf-postbox'),
    					$errorFields = $tab.parents('.acf-fields').children('.acf-field.acf-error');
    
    					if ($errorFields.length === 0) {
    						$tab.removeClass(self.errorClass);
    						$group.removeClass(self.errorClass);
    					}
    				});
    				return this;
    			},
    			proxy: function(func) {
    
    				// Simple proxy function to ensure the context of a callback function is this object's instance.
    				
    				var self = this;
            return function() {
                return func.apply(self, arguments);
            };
    			}
    		};
    
    		
    
    		$(function () {
    
    			var $form = $('#wpbody form');
    
    			TabErrorMarker.init($form);
    		});
    
    	});
    
    })(jQuery);
    

    And here is the CSS to highlight the tabs and field groups that have errors.

    
    .acf-tab-button.acf-tab-validation-errors {
    	border-color: #F55E4F !important;
    	color: #F55E4F !important;
    }
    .acf-postbox.acf-tab-validation-errors > .hndle {
    	border-color: #F55E4F !important;
    	color: #F55E4F !important;
    }
    .acf-postbox.acf-tab-validation-errors > .handlediv {
    	color: #F55E4F !important;
    }
    

    The result looks like the image below.

    ACF Tab Error Highlight

  • When you have a flexible content area and layouts withs tabs:

    acf.add_filter('validation_complete', function(json, $form){
    	if(json.errors) {
        $.each(json.errors, function(key) {
          const field = $('[name="' + json.errors[key].input + '"]', $form).parents('.acf-field');
          const parent_field = field[field.length - 2];
          const tab = $(parent_field, $form).prev('.acf-field-tab').attr('data-key');
          $('.acf-tab-wrap a[data-key=' + tab + ']', $form).click();
        });
    	}
    
    	return json;
    });
  • @marcelo2605 thanks for your solution.
    This one also works for repeater fields:

    jQuery(document).ready(function ($) {
        acf.add_filter('validation_complete', function (json) {
            if (json.errors) {
                $.each(json.errors, function (index) {
                    var field = $('[name="' + json.errors[index].input + '"]').parents('.acf-field');
                    var repeater = field[1];
                    var previous_tabs = $(repeater).prevAll('[data-type="tab"]');
                    var tab_data_key = $(previous_tabs[0]).attr('data-key');
                    $('.acf-tab-wrap a[data-key=' + tab_data_key + ']').click();
                });
            }
    
            return json;
        });
    });
  • This one opens only the first error and then acf scrolls to it. But it works also with nested tabs and in repeaters. Probably in layouts too, but I haven’t tested it.

    
    acf.add_filter('validation_complete', function (json) {
        if (json.errors) {
            var input = json.errors.shift().input;
            $('[name="' + input + '"]').parents('.acf-field').each(function(){
                var $tab = $(this).prevAll('[data-type="tab"]').first();
                if($tab.length){
                    var tab_data_key = $tab.attr('data-key');
                    $tab.prevAll('.acf-tab-wrap').find('a[data-key="' + tab_data_key + '"]').click();
                }
            });
        }
    
        return json;
    });
  • Just a little error in my code. There shouldn’t be shift() on line 3.

    acf.add_filter('validation_complete', function (json) {
        if (json.errors) {
            var input = json.errors[0].input;
            $('[name="' + input + '"]').parents('.acf-field').each(function(){
                var $tab = $(this).prevAll('[data-type="tab"]').first();
                if($tab.length){
                    var tab_data_key = $tab.attr('data-key');
                    $tab.prevAll('.acf-tab-wrap').find('a[data-key="' + tab_data_key + '"]').click();
                }
            });
        }
    
        return json;
    });
  • This is what I’ve been looking for; a way of taking the user to the tab where a required field is blank. But where do I insert the codes?

    Thanks.

    Martin

  • @martinkoss
    The simplest way is to install and activate this plugin:
    https://wordpress.org/plugins/insert-headers-and-footers/

    Then go to “Settings » Insert Headers and Footers” and add the code above into the “Scripts in Footer” section and save.

Viewing 10 posts - 1 through 10 (of 10 total)

You must be logged in to reply to this topic.