FormValidation v0.8.1 is released, supports Bootstrap 4 alpha 3

Validating percentage values

Examples

This example introduces the approach to validate percentage fields which have to satisfy two conditions:

  • Each field must present a number between 0 and 100
  • The sum of all field values have to equal to 100

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'
                }
            }
        }
    }
});
Don't forget to wrap the field name between single or double quotes when validating the field with special name

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:

Using 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/examples/adjusting-feedback-icon-position/
*/
#sumForm .sliderContainer .form-control-feedback {
    top: -6px;
}
</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>