Home › Forums › Front-end Issues › 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.
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.
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.
The topic ‘Sending user to Tab on validation errors’ is closed to new replies.
Welcome to the Advanced Custom Fields community forum.
Browse through ideas, snippets of code, questions and answers between fellow ACF users
Helping others is a great way to earn karma, gain badges and help ACF development!
We use cookies to offer you a better browsing experience, analyze site traffic and personalize content. Read about how we use cookies and how you can control them in our Privacy Policy. If you continue to use this site, you consent to our use of cookies.