Using usual text inputs
To find out the solution for our requirement, we can think of using the normal text
inputs for percentage fields. ALl the inputs have the same name and are initialized with
default value as following:
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
<!-- More if you want -->
Now, for the first condition, we can use the between
validator to ask the percentage to be between 0 to 100:
$ ( '#sumForm' ). formValidation ({
fields : {
'percentage[]' : {
validators : {
between : {
min : 0 ,
max : 100 ,
message : 'The percentage must be between 0 and 100'
}
}
}
}
});
In order to solve the second requirement, we need to use the callback validator. The idea is quite simple:
Calculate the sum of all fields by looping over them
Compare the sum with 100. If the sum equals to 100 exactly, we treat all fields as
valid by using the updateStatus() method.
The following snippet code shows how it is implemented:
$ ( '#sumForm' ). formValidation ({
fields : {
'percentage[]' : {
validators : {
between : {
...
},
callback : {
message : 'The sum of percentages must be 100' ,
callback : function ( value , validator , $field ) {
var percentage = validator . getFieldElements ( 'percentage[]' ),
length = percentage . length ,
sum = 0 ;
for ( var i = 0 ; i < length ; i ++ ) {
sum += parseFloat ( $ ( percentage [ i ]). val ());
}
if ( sum === 100 ) {
validator . updateStatus ( 'percentage[]' , 'VALID' , 'callback' );
return true ;
}
return false ;
}
}
}
}
}
});
You can try the implementation by playing with the form below:
< form id = "sumForm" method = "post" class = "form-horizontal" >
< div class = "form-group" >
< label class = "col-xs-3 control-label" > Percentages</ label >
< div class = "col-xs-4" >
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
</ div >
</ div >
< div class = "form-group" >
< div class = "col-xs-4 col-xs-offset-3" >
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
</ div >
</ div >
< div class = "form-group" >
< div class = "col-xs-4 col-xs-offset-3" >
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
</ div >
</ div >
< div class = "form-group" >
< div class = "col-xs-4 col-xs-offset-3" >
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
</ div >
</ div >
< div class = "form-group" >
< div class = "col-xs-9 col-xs-offset-3" >
< button type = "submit" class = "btn btn-primary" > Validate</ button >
</ div >
</ div >
</ form >
< script >
$ ( document ). ready ( function () {
$ ( '#sumForm' ). formValidation ({
framework : 'bootstrap' ,
icon : {
valid : 'glyphicon glyphicon-ok' ,
invalid : 'glyphicon glyphicon-remove' ,
validating : 'glyphicon glyphicon-refresh'
},
fields : {
'percentage[]' : {
// Setting verbose to false ensures that
// the callback validator is only performed
// when the field already passes the between validator
verbose : false ,
validators : {
between : {
min : 0 ,
max : 100 ,
message : 'The percentage must be between 0 and 100'
},
callback : {
message : 'The sum of percentages must be 100' ,
callback : function ( value , validator , $field ) {
var percentage = validator . getFieldElements ( 'percentage[]' ),
length = percentage . length ,
sum = 0 ;
for ( var i = 0 ; i < length ; i ++ ) {
sum += parseFloat ( $ ( percentage [ i ]). val ());
}
if ( sum === 100 ) {
validator . updateStatus ( 'percentage[]' , 'VALID' , 'callback' );
return true ;
}
return false ;
}
}
}
}
}
});
});
</ script >
Showing only one message
If you click the Validate button in the form above, because all fields
don't pass the callback
validator, all messages are shown up.
We can optimize it a little bit by using the err
option to indicate the same container for placing messages. Since we use the same name
for fields, there's only one message shown at any time.
< div class = "form-group" >
< div class = "col-xs-9 col-xs-offset-3" >
<!-- The message container -->
< div id = "messageContainer" ></ div >
</ div >
</ div >
$ ( '#sumForm' ). formValidation ({
fields : {
'percentage[]' : {
// The message container
err : '#messageContainer' ,
validators : {
...
}
}
}
});
The following image shows how the messages are shown when using (and not) the
err
option:
< form id = "sumForm" method = "post" class = "form-horizontal" >
< div class = "form-group" >
< label class = "col-xs-3 control-label" > Percentages</ label >
< div class = "col-xs-4" >
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
</ div >
</ div >
< div class = "form-group" >
< div class = "col-xs-4 col-xs-offset-3" >
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
</ div >
</ div >
< div class = "form-group" >
< div class = "col-xs-4 col-xs-offset-3" >
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
</ div >
</ div >
< div class = "form-group" >
< div class = "col-xs-4 col-xs-offset-3" >
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
</ div >
</ div >
< div class = "form-group" >
< div class = "col-xs-9 col-xs-offset-3" >
<!-- The message container -->
< div id = "messageContainer" ></ div >
</ div >
</ div >
< div class = "form-group" >
< div class = "col-xs-9 col-xs-offset-3" >
< button type = "submit" class = "btn btn-primary" > Validate</ button >
</ div >
</ div >
</ form >
< script >
$ ( document ). ready ( function () {
$ ( '#sumForm' ). formValidation ({
framework : 'bootstrap' ,
icon : {
valid : 'glyphicon glyphicon-ok' ,
invalid : 'glyphicon glyphicon-remove' ,
validating : 'glyphicon glyphicon-refresh'
},
fields : {
'percentage[]' : {
// Setting verbose to false ensures that
// the callback validator is only performed
// when the field already passes the between validator
verbose : false ,
// The message container
err : '#messageContainer' ,
validators : {
between : {
min : 0 ,
max : 100 ,
message : 'The percentage must be between 0 and 100'
},
callback : {
message : 'The sum of percentages must be 100' ,
callback : function ( value , validator , $field ) {
var percentage = validator . getFieldElements ( 'percentage[]' ),
length = percentage . length ,
sum = 0 ;
for ( var i = 0 ; i < length ; i ++ ) {
sum += parseFloat ( $ ( percentage [ i ]). val ());
}
if ( sum === 100 ) {
validator . updateStatus ( 'percentage[]' , 'VALID' , 'callback' );
return true ;
}
return false ;
}
}
}
}
}
});
});
</ script >
Using a slider
Instead of the normal text inputs, we can use a slider to allow user to choose a
percentage value between 0 and 100. In this approach, we will use the Bootstrap Slider which works well
with the Bootstrap framework.
Using the slider ensures the value will belong to the given ranges, so the between validator isn't necessary in this case.
After dragging the slider, we need to revalidate the
associated input:
$ ( '#sumForm' )
. find ( '[name="percentage[]"]' )
. each ( function () {
$ ( this )
// Create a slider
. slider ({
min : 0 ,
max : 100 ,
step : 1 ,
tooltip : 'hide'
})
// Triggered after dragging the slider
. on ( 'slide' , function ( e ) {
var $field = $ ( e . target );
$field
. closest ( '.form-group' )
. find ( '.percentageValue' )
. html ( $field . slider ( 'getValue' ) + '%' );
// Revalidate the field
$ ( '#sumForm' ). formValidation ( 'revalidateField' , $field );
});
})
Take a look at these
principles when integrating FormValidation with
other plugins
< link rel = "stylesheet" href = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/4.10.0/css/bootstrap-slider.min.css" />
< style type = "text/css" >
/*
Adjust feedback icon position:
http://formvalidation.io/ejemplos/adjusting-feedback-icon-position/
*/
# sumForm . sliderContainer . form-control-feedback {
top : -6 px ;
}
</ style >
< form id = "sumForm" method = "post" class = "form-horizontal" >
< div class = "form-group" >
< label class = "col-xs-3 control-label" > Percentages</ label >
< div class = "col-xs-5 sliderContainer" >
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
</ div >
< div class = "col-xs-3" >
< span class = "percentageValue" > 0%</ span >
</ div >
</ div >
< div class = "form-group" >
< div class = "col-xs-5 col-xs-offset-3 sliderContainer" >
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
</ div >
< div class = "col-xs-3" >
< span class = "percentageValue" > 0%</ span >
</ div >
</ div >
< div class = "form-group" >
< div class = "col-xs-5 col-xs-offset-3 sliderContainer" >
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
</ div >
< div class = "col-xs-3" >
< span class = "percentageValue" > 0%</ span >
</ div >
</ div >
< div class = "form-group" >
< div class = "col-xs-5 col-xs-offset-3 sliderContainer" >
< input class = "form-control" name = "percentage[]" type = "text" value = "0" />
</ div >
< div class = "col-xs-3" >
< span class = "percentageValue" > 0%</ span >
</ div >
</ div >
< div class = "form-group" >
< div class = "col-xs-9 col-xs-offset-3" >
<!-- The message container -->
< div id = "messageContainer" ></ div >
</ div >
</ div >
< div class = "form-group" >
< div class = "col-xs-9 col-xs-offset-3" >
< button type = "submit" class = "btn btn-primary" > Validate</ button >
</ div >
</ div >
</ form >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/4.10.0/bootstrap-slider.min.js" ></ script >
< script >
$ ( document ). ready ( function () {
$ ( '#sumForm' )
. find ( '[name="percentage[]"]' )
. each ( function () {
$ ( this )
// Create a slider
. slider ({
min : 0 ,
max : 100 ,
step : 1 ,
tooltip : 'hide'
})
// Triggered after dragging the slider
. on ( 'slide' , function ( e ) {
var $field = $ ( e . target );
$field
. closest ( '.form-group' )
. find ( '.percentageValue' )
. html ( $field . slider ( 'getValue' ) + '%' );
// Revalidate the field
$ ( '#sumForm' ). formValidation ( 'revalidateField' , $field );
});
})
. end ()
. formValidation ({
framework : 'bootstrap' ,
icon : {
valid : 'glyphicon glyphicon-ok' ,
invalid : 'glyphicon glyphicon-remove' ,
validating : 'glyphicon glyphicon-refresh'
},
excluded : ':disabled' ,
fields : {
'percentage[]' : {
// Setting verbose to false ensures that
// the callback validator is only performed
// when the field already passes the between validator
verbose : false ,
err : '#messageContainer' ,
validators : {
callback : {
message : 'The sum of percentages must be 100' ,
callback : function ( value , validator , $field ) {
var percentage = validator . getFieldElements ( 'percentage[]' ),
length = percentage . length ,
sum = 0 ;
for ( var i = 0 ; i < length ; i ++ ) {
sum += parseInt ( $ ( percentage [ i ]). val (), 10 );
console . log ( parseInt ( $ ( percentage [ i ]). val (), 10 ), sum );
}
if ( sum === 100 ) {
validator . updateStatus ( 'percentage[]' , 'VALID' , 'callback' );
return true ;
}
return false ;
}
}
}
}
}
});
});
</ script >