Using JQuery Validate Plugin to validate multiple form fields with identical names
JqueryValidationJquery ValidateJquery Problem Overview
I have a dynamically generated form with input fields with the same name (for example: "map"). I do not have the option of changing the field names or generating unique field names because the form handler code (Perl/CGI) is designed to handle an array of input values (in this case @map
).
How can I use the JQuery [Validate Plugin][1] to validate a form in such a situation? Specifically I would want exactly one element of the submitted array to have a certain fixed value. I am currently using a custom event handler that creates a JSON object with serializeArray()
and then traverses it to ensure that the condition is met. But since I have used the Validate Plugin in the rest of the application, I was wondering if such a case may be handled using the same plugin here too.
Thank you for your attention.
[1]: http://docs.jquery.com/Plugins/Validation "Validation"
Jquery Solutions
Solution 1 - Jquery
Instead of changing the source file jquery.validation you can simply override the function you need to edit only in the pages that requires it.
An example would be:
$.validator.prototype.checkForm = function() {
//overriden in a specific page
this.prepareForm();
for (var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++) {
if (this.findByName(elements[i].name).length !== undefined && this.findByName(elements[i].name).length > 1) {
for (var cnt = 0; cnt < this.findByName(elements[i].name).length; cnt++) {
this.check(this.findByName(elements[i].name)[cnt]);
}
} else {
this.check(elements[i]);
}
}
return this.valid();
};
This might not be the best solution, but at least it avoids editing source files, that could be replaced later, when a new version releases. Where your overridden function might or might not break.
Solution 2 - Jquery
Old thread I know but I came across it in search of the fix to the same problem.
A more elegant solution has been posted here: http://web-funda.blogspot.com/2009/05/jquery-validation-for-array-of-input.html
You simply edit jquery.validate.js and change the checkForm to
checkForm: function() {
this.prepareForm();
for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) {
if (this.findByName( elements[i].name ).length != undefined && this.findByName( elements[i].name ).length > 1) {
for (var cnt = 0; cnt < this.findByName( elements[i].name ).length; cnt++) {
this.check( this.findByName( elements[i].name )[cnt] );
}
} else {
this.check( elements[i] );
}
}
return this.valid();
}
Solution 3 - Jquery
I spent some time searching and trying different things when finally I tried the most trivial way of doing validation on multiple fields. Each field and it's clones share a class unique to each set. I just looped through the inputs with that class and added my validation rules as usual. I hope this might help someone else.
$("#submit").click(function(){
$("input.years").each(function(){
$(this).rules("add", {
required: true,
messages: {
required: "Specify the years you worked"
}
} );
});
$("input.employerName").each(function(){
$(this).rules("add", {
required: true,
messages: {
required: "Specify the employer name"
}
} );
});
$("input.employerPhone").each(function(){
$(this).rules("add", {
required: true,
minlength: 10,
messages: {
required: "Specify the employer phone number",
minlength: "Not long enough"
}
} );
});
$("input.position").each(function(){
$(this).rules("add", {
required: true,
messages: {
required: "Specify your position"
}
} );
});
$("input.referenceName").each(function(){
$(this).rules("add", {
required: true,
messages: {
required: "Specify the reference name"
}
} );
});
$("input.referencePhone").each(function(){
$(this).rules("add", {
required: true,
minlength: 10,
messages: {
required: "Specify your reference phone number",
minlength: "Not long enough"
}
} );
});
// Now do your normal validation here, but don't assign rules/messages for the fields we just set them for
});
Solution 4 - Jquery
I just learned from a mail by the Plugins author, Jörn Zaefferer, that validation requires field names to be unique except for radio buttons and check boxes.
Solution 5 - Jquery
Jason's answer will do the trick, but I didn't want to add extra click events on every form I did this on.
In my case, I have the validation plugin consider names ending with '[]' different even though they may have identical fieldnames. To do this, I overwrote these two internal methods after jquery.validate.js loads.
$.validator.prototype.elements= function() {
var validator = this,
rulesCache = {};
// select all valid inputs inside the form (no submit or reset buttons)
// workaround $Query([]).add until http://dev.jquery.com/ticket/2114 is solved
return $([]).add(this.currentForm.elements)
.filter(":input")
.not(":submit, :reset, :image, [disabled]")
.not( this.settings.ignore )
.filter(function() {
!this.name && validator.settings.debug && window.console && console.error( "%o has no name assigned", this);
// select only the first element for each name (EXCEPT elements that end in []), and only those with rules specified
if ( (!this.name.match(/\[\]/gi) && this.name in rulesCache) || !validator.objectLength($(this).rules()) )
return false;
rulesCache[this.name] = true;
return true;
});
};
$.validator.prototype.idOrName = function(element) {
// Special edit to get fields that end with [], since there are several [] we want to disambiguate them
// Make an id on the fly if the element doesnt have one
if(element.name.match(/\[\]/gi)) {
if(element.id){
return element.id;
} else {
var unique_id = new Date().getTime();
element.id = new Date().getTime();
return element.id;
}
}
return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name);
};
Solution 6 - Jquery
Simply use an unused attribute of the input to store the original name, then just rename with it's index attached:
function addMultiInputNamingRules(form, field, rules){
$(form).find(field).each(function(index){
$(this).attr('alt', $(this).attr('name'));
$(this).attr('name', $(this).attr('name')+'-'+index);
$(this).rules('add', rules);
});
}
function removeMultiInputNamingRules(form, field){
$(form).find(field).each(function(index){
$(this).attr('name', $(this).attr('alt'));
$(this).removeAttr('alt');
});
}
Then after you set your validator:
addMultiInputNamingRules('#form-id', 'input[name="multifield[]"]', { required:true });
and when you've finished validating, revert back like so:
removeMultiInputNamingRules('#form-id', 'input[alt="multifield[]"]');
-- Hope this helps!
Solution 7 - Jquery
I'm using "jQuery validation plug-in 1.7".
The problem why multiple "$(:input)" elements sharing the same name are not validated
is the $.validator.element method:
elements: function() {
var validator = this,
rulesCache = {};
// select all valid inputs inside the form (no submit or reset buttons)
// workaround $Query([]).add until http://dev.jquery.com/ticket/2114 is solved
return $([]).add(this.currentForm.elements)
.filter(":input")
.not(":submit, :reset, :image, [disabled]")
.not( this.settings.ignore )
.filter(function() {
!this.name && validator.settings.debug && window.console && console.error( "%o has no name assigned", this);
// select only the first element for each name, and only those with rules specified
if ( this.name in rulesCache || !validator.objectLength($(this).rules()) )
return false;
rulesCache[this.name] = true;
return true;
});
},
The condition
if ( this.name in rulesCache ||.....
evaluates for the second and next elements sharing the same name true....
The solution would be having the condition:
(this.id || this.name) in rulesCache
Excuse me, JS puritans, that (this.id || this.name) is not at 100%...
Of course, the
rulesCache[this.name] = true;
line must be changed appropriately as well.
So the $.validator.prototype.elements method would be:
$(function () {
if ($.validator) {
//fix: when several input elements shares the same name, but has different id-ies....
$.validator.prototype.elements = function () {
var validator = this,
rulesCache = {};
// select all valid inputs inside the form (no submit or reset buttons)
// workaround $Query([]).add until http://dev.jquery.com/ticket/2114 is solved
return $([]).add(this.currentForm.elements)
.filter(":input")
.not(":submit, :reset, :image, [disabled]")
.not(this.settings.ignore)
.filter(function () {
var elementIdentification = this.id || this.name;
!elementIdentification && validator.settings.debug && window.console && console.error("%o has no id nor name assigned", this);
// select only the first element for each name, and only those with rules specified
if (elementIdentification in rulesCache || !validator.objectLength($(this).rules()))
return false;
rulesCache[elementIdentification] = true;
return true;
});
};
}
});
Solution 8 - Jquery
Here is how I did it. A bit easier than previously proposed methods:
function validateTab(tab) {
var valid = true;
$(tab).find('input').each(function (index, elem) {
var isElemValid = $("#registrationForm").validate().element(elem);
if (isElemValid != null) { //this covers elements that have no validation rule
valid = valid & isElemValid;
}
});
return valid;
}
In my case I have a wizard (of 3 steps) which turned out to be even more complex as I don't want to validate all fields at once. I basically place components in tabs and if first tab is valid, I move to the next, until I get to last one, after which I submit all data. Thus the tab
parameter there is the actual tab element (which is a div
). I then loop through all input elements children to my tab and check them for validity.
Everything else is standard.
Just for completeness here is the rest of the code: how the form submit is done and how my validator looks like:
<a href="javascript:moveToNextTab(1)" class="button next">Submit</a>
And here the js function called:
function moveToNextTab(currentTab) {
var tabs = document.getElementsByClassName("tab");
//loop through tabs and validate the current one.
//If valid, hide current tab and make next one visible.
}
I'm using these validation rules (which I create on JQuery.ready):
$("#registrationForm").validate({
rules: {
birthdate: {
required: true,
date: true
},
name: "required",
surname: "required",
address: "required",
postalCode: "required",
city: "required",
country: "required",
email: {
required: true,
email: true
}
}
});
Solution 9 - Jquery
Maybe I'm missing the point, but since the validator doesn't work with multiple names (tried... failed!) I changed my form to dynamically change the names, set the rules, then unset the names on submit.
Two methods (ignore the wlog stuff, it just outputs to the console):
// convert the field names into generated ones to allow fields with the same names
// to be validated individually. The original names are stored as data against the
// elements, ready to be replaced. The name is replaced with
// "multivalidate-<name>-<id>", e.g. original => 'multivalidate-original-1'
function setGeneratedNamesWithValidationRules(form, fields, rules) {
var length = fields.length;
for (var i=0; i < length; ++i ){
var name = fields[i];
var idCounter = 0;
// we match either the already converted generator names or the original
$("form [name^='multivalidate-" + name + "'], form [name='" + name + "']").each(function() {
// identify the real name, either from the stored value, or the actual name attribute
var realName = $(this).data('realName');
if (realName == undefined) {
realName = $(this).attr("name");
$(this).data('realName', realName);
}
wlog("Name: " + realName + " (actual: " + $(this).attr("name") + "), val: " + $(this).val() + ". Rules: " + rules[realName]);
$(this).attr("name", "multivalidate-" + realName + "-" + idCounter);
if (rules[realName]) {
$(this).rules("add", rules[realName]);
}
idCounter++;
});
}
}
function revertGeneratedNames(form, fields) {
var length = fields.length;
for (var i=0; i < length; ++i ){
var name = fields[i];
wlog("look for fields names [" + name + "]");
$("form [name^='multivalidate-" + name + "']").each(function() {
var realName = $(this).data('realName');
if (realName == undefined) {
wlog("Error: field named [" + $(this).attr("name") + "] does not have a stored real name");
} else {
wlog("Convert [" + $(this).attr("name") + "] back to [" + realName + "]");
$(this).attr("name", realName);
}
});
}
}
On the form load, and whenever I dynamically add another row, I call the set method, e.g.
setGeneratedNamesWithValidationRules($("#my-dynamic-form"), ['amounts'], { 'amounts': 'required'} );
This changes the names to allow individual validation.
In the submitHandler: thingumy after validation I call the revert, i.e.
revertGeneratedNames(form, ['amounts']);
Which switches the names back to the originals before posting the data.
Solution 10 - Jquery
For me this was solved very easily by disabling the debug
$("#_form").validate({
debug:false,
//debug: true,
...
});
Solution 11 - Jquery
There is a simple solution:
$(document).ready(function() {
$(".form").each(function() {
$(this).validate({
...
,errorContainer: $(".status_mess",this) // use "this" as reference to that instance of form.
...
});
});
});
Solution 12 - Jquery
I think you misunderstood the workings of HTML forms. Every form element needs to have an unique name, except multiple checkboxes and buttons that allow you to choose one/multiple options for one data field.
In your case, not only JQuery validation, but also a server-side form validator would fail, because it can't assign the inputs to the data fields. Suppose, you want the user to enter prename, lastname, e-mail-adress, fax (optional) and all your input fields have name="map"
Then you would receive these lists on submit:
map = ['Joe','Doe','joe.doeAThotmail.com','++22 20182238'] //All fields completed
map = ['Joe','Doe','joe.doeAThotmail.com'] //OK, all mandatory fields completed
map = ['Doe', 'joe.doeAThotmail.com','++22 20182238']//user forgot prename, should yield error
You see that it is impossible to validate this form reliably.
I recommend to revisit the documentation of your perl form handler or adapt it if you wrote it on your own.