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

Adding dynamic field

Examples

When working with complex form, the fields might be added to (or remove from) the form dynamically. The newly added fields also need to be validated.

This example demonstrates a sample scenario where you have to solve validating dynamic fields problem.

Before going to the details, there are some methods and events you need to know:

Method/Event Description
addField() method Adding new field
removeField() method Removing given field
added.field.fv event Called after adding new field
removed.field.fv event Called after removing given field

Adding fields with same names

Assuming that you are managing a survey which consists of a question and some options that user can choose from. The form allows you to add more options but the number of options can't exceed 5.

<form id="surveyForm" method="post" class="form-horizontal">
    <div class="form-group">
        <label class="col-xs-3 control-label">Question</label>
        <div class="col-xs-5">
            <input type="text" class="form-control" name="question" />
        </div>
    </div>

    <div class="form-group">
        <label class="col-xs-3 control-label">Options</label>
        <div class="col-xs-5">
            <input type="text" class="form-control" name="option[]" />
        </div>
        <div class="col-xs-4">
            <button type="button" class="btn btn-default addButton"><i class="fa fa-plus"></i></button>
        </div>
    </div>

    <!-- The option field template containing an option field and a Remove button -->
    <div class="form-group hide" id="optionTemplate">
        <div class="col-xs-offset-3 col-xs-5">
            <input class="form-control" type="text" name="option[]" />
        </div>
        <div class="col-xs-4">
            <button type="button" class="btn btn-default removeButton"><i class="fa fa-minus"></i></button>
        </div>
    </div>

    <div class="form-group">
        <div class="col-xs-5 col-xs-offset-3">
            <button type="submit" class="btn btn-default">Validate</button>
        </div>
    </div>
</form>

<script>
$(document).ready(function() {
    // The maximum number of options
    var MAX_OPTIONS = 5;

    $('#surveyForm')
        .formValidation({
            framework: 'bootstrap',
            icon: {
                valid: 'glyphicon glyphicon-ok',
                invalid: 'glyphicon glyphicon-remove',
                validating: 'glyphicon glyphicon-refresh'
            },
            fields: {
                question: {
                    validators: {
                        notEmpty: {
                            message: 'The question required and cannot be empty'
                        }
                    }
                },
                'option[]': {
                    validators: {
                        notEmpty: {
                            message: 'The option required and cannot be empty'
                        },
                        stringLength: {
                            max: 100,
                            message: 'The option must be less than 100 characters long'
                        }
                    }
                }
            }
        })

        // Add button click handler
        .on('click', '.addButton', function() {
            var $template = $('#optionTemplate'),
                $clone    = $template
                                .clone()
                                .removeClass('hide')
                                .removeAttr('id')
                                .insertBefore($template),
                $option   = $clone.find('[name="option[]"]');

            // Add new field
            $('#surveyForm').formValidation('addField', $option);
        })

        // Remove button click handler
        .on('click', '.removeButton', function() {
            var $row    = $(this).parents('.form-group'),
                $option = $row.find('[name="option[]"]');

            // Remove element containing the option
            $row.remove();

            // Remove field
            $('#surveyForm').formValidation('removeField', $option);
        })

        // Called after adding new field
        .on('added.field.fv', function(e, data) {
            // data.field   --> The field name
            // data.element --> The new field element
            // data.options --> The new field options

            if (data.field === 'option[]') {
                if ($('#surveyForm').find(':visible[name="option[]"]').length >= MAX_OPTIONS) {
                    $('#surveyForm').find('.addButton').attr('disabled', 'disabled');
                }
            }
        })

        // Called after removing the field
        .on('removed.field.fv', function(e, data) {
           if (data.field === 'option[]') {
                if ($('#surveyForm').find(':visible[name="option[]"]').length < MAX_OPTIONS) {
                    $('#surveyForm').find('.addButton').removeAttr('disabled');
                }
            }
        });
});
</script>

Adding fields with different names

The following form manages the wish list of books you would love to buy. Each book must have three properties which are title, ISBN number and price (in dollar).

Assume that, due to the required naming convention from server, the fields for these properties are named as book[i].title, book[i].isbn and book[i].price, where i is the index number of book which can be 0, 1, 2, and so forth.

The example uses the row option to indicate the field container when using multiple fields in the same row
<form id="bookForm" method="post" class="form-horizontal">
    <div class="form-group">
        <label class="col-xs-1 control-label">Book</label>
        <div class="col-xs-4">
            <input type="text" class="form-control" name="book[0].title" placeholder="Title" />
        </div>
        <div class="col-xs-4">
            <input type="text" class="form-control" name="book[0].isbn" placeholder="ISBN" />
        </div>
        <div class="col-xs-2">
            <input type="text" class="form-control" name="book[0].price" placeholder="Price" />
        </div>
        <div class="col-xs-1">
            <button type="button" class="btn btn-default addButton"><i class="fa fa-plus"></i></button>
        </div>
    </div>

    <!-- The template for adding new field -->
    <div class="form-group hide" id="bookTemplate">
        <div class="col-xs-4 col-xs-offset-1">
            <input type="text" class="form-control" name="title" placeholder="Title" />
        </div>
        <div class="col-xs-4">
            <input type="text" class="form-control" name="isbn" placeholder="ISBN" />
        </div>
        <div class="col-xs-2">
            <input type="text" class="form-control" name="price" placeholder="Price" />
        </div>
        <div class="col-xs-1">
            <button type="button" class="btn btn-default removeButton"><i class="fa fa-minus"></i></button>
        </div>
    </div>

    <div class="form-group">
        <div class="col-xs-5 col-xs-offset-1">
            <button type="submit" class="btn btn-default">Submit</button>
        </div>
    </div>
</form>

<script>
$(document).ready(function() {
    var titleValidators = {
            row: '.col-xs-4',   // The title is placed inside a <div class="col-xs-4"> element
            validators: {
                notEmpty: {
                    message: 'The title is required'
                }
            }
        },
        isbnValidators = {
            row: '.col-xs-4',
            validators: {
                notEmpty: {
                    message: 'The ISBN is required'
                },
                isbn: {
                    message: 'The ISBN is not valid'
                }
            }
        },
        priceValidators = {
            row: '.col-xs-2',
            validators: {
                notEmpty: {
                    message: 'The price is required'
                },
                numeric: {
                    message: 'The price must be a numeric number'
                }
            }
        },
        bookIndex = 0;

    $('#bookForm')
        .formValidation({
            framework: 'bootstrap',
            icon: {
                valid: 'glyphicon glyphicon-ok',
                invalid: 'glyphicon glyphicon-remove',
                validating: 'glyphicon glyphicon-refresh'
            },
            fields: {
                'book[0].title': titleValidators,
                'book[0].isbn': isbnValidators,
                'book[0].price': priceValidators
            }
        })

        // Add button click handler
        .on('click', '.addButton', function() {
            bookIndex++;
            var $template = $('#bookTemplate'),
                $clone    = $template
                                .clone()
                                .removeClass('hide')
                                .removeAttr('id')
                                .attr('data-book-index', bookIndex)
                                .insertBefore($template);

            // Update the name attributes
            $clone
                .find('[name="title"]').attr('name', 'book[' + bookIndex + '].title').end()
                .find('[name="isbn"]').attr('name', 'book[' + bookIndex + '].isbn').end()
                .find('[name="price"]').attr('name', 'book[' + bookIndex + '].price').end();

            // Add new fields
            // Note that we also pass the validator rules for new field as the third parameter
            $('#bookForm')
                .formValidation('addField', 'book[' + bookIndex + '].title', titleValidators)
                .formValidation('addField', 'book[' + bookIndex + '].isbn', isbnValidators)
                .formValidation('addField', 'book[' + bookIndex + '].price', priceValidators);
        })

        // Remove button click handler
        .on('click', '.removeButton', function() {
            var $row  = $(this).parents('.form-group'),
                index = $row.attr('data-book-index');

            // Remove fields
            $('#bookForm')
                .formValidation('removeField', $row.find('[name="book[' + index + '].title"]'))
                .formValidation('removeField', $row.find('[name="book[' + index + '].isbn"]'))
                .formValidation('removeField', $row.find('[name="book[' + index + '].price"]'));

            // Remove element containing the fields
            $row.remove();
        });
});
</script>

Using other plugin for added fields

Sometime, the added fields aren't just simply a normal input such as text box, text area. They can be used with other plugins such as a date picker, an auto-completed control, etc.

In this case, the field should be initially as a normal text input, and then integrated with other plugin after being added.

The following sample code attaches a date picker to newly added input by triggering the added.field.fv event:

$('#taskForm')
    .formValidation({
        ...
    })
    .on('added.field.fv', function(e, data) {
        // data.field   --> The field name
        // data.element --> The new field element
        // data.options --> The new field options

        if (data.field === 'dueDate[]') {
            // The new due date field is just added
            // Create a new date picker
            data.element
                .parent()
                .datepicker({
                    format: 'yyyy/mm/dd'
                })
                .on('changeDate', function(evt) {
                    // Revalidate the date field
                    $('#taskForm').formValidation('revalidateField', data.element);
                });
        }
    });
You should look at the basic principles when integrating FormValidation with other plugins
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.3.0/css/datepicker.min.css" />
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.3.0/css/datepicker3.min.css" />
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.3.0/js/bootstrap-datepicker.min.js"></script>

<form id="taskForm" method="post" class="form-horizontal">
    <div class="form-group">
        <label class="col-xs-1 control-label">Task(s)</label>
        <div class="col-xs-6">
            <input type="text" class="form-control" name="task[]" placeholder="Task" />
        </div>
        <div class="col-xs-4 dateContainer">
            <div class="input-group input-append date" id="dueDatePicker">
                <span class="input-group-addon add-on"><span class="glyphicon glyphicon-calendar"></span></span>
                <input type="text" class="form-control" name="dueDate[]" placeholder="Due date" />
            </div>
        </div>
        <div class="col-xs-1">
            <button type="button" class="btn btn-default addButton"><i class="fa fa-plus"></i></button>
        </div>
    </div>

    <!-- The template for adding new field -->
    <div class="form-group hide" id="taskTemplate">
        <div class="col-xs-6 col-xs-offset-1">
            <input type="text" class="form-control" name="task[]" placeholder="Task" />
        </div>
        <div class="col-xs-4 dateContainer">
            <div class="input-group input-append date">
                <span class="input-group-addon add-on"><span class="glyphicon glyphicon-calendar"></span></span>
                <input type="text" class="form-control" name="dueDate[]" placeholder="Due date" />
            </div>
        </div>
        <div class="col-xs-1">
            <button type="button" class="btn btn-default removeButton"><i class="fa fa-minus"></i></button>
        </div>
    </div>

    <div class="form-group">
        <div class="col-xs-5 col-xs-offset-1">
            <button type="submit" class="btn btn-default">Submit</button>
        </div>
    </div>
</form>

<script>
$(document).ready(function() {
    // Initialize the date picker for the original due date field
    $('#dueDatePicker')
        .datepicker({
            format: 'yyyy/mm/dd'
        })
        .on('changeDate', function(evt) {
            // Revalidate the date field
            $('#taskForm').formValidation('revalidateField', $('#dueDatePicker').find('[name="dueDate[]"]'));
        });

    $('#taskForm')
        .formValidation({
            framework: 'bootstrap',
            icon: {
                valid: 'glyphicon glyphicon-ok',
                invalid: 'glyphicon glyphicon-remove',
                validating: 'glyphicon glyphicon-refresh'
            },
            fields: {
                'task[]': {
                    // The task is placed inside a .col-xs-6 element
                    row: '.col-xs-6',
                    validators: {
                        notEmpty: {
                            message: 'The task is required'
                        }
                    }
                },
                'dueDate[]': {
                    // The due date is placed inside a .col-xs-4 element
                    row: '.col-xs-4',
                    validators: {
                        notEmpty: {
                            message: 'The due date is required'
                        },
                        date: {
                            format: 'YYYY/MM/DD',
                            min: new Date(),
                            message: 'The due date is not valid'
                        }
                    }
                }
            }
        })

        .on('added.field.fv', function(e, data) {
            if (data.field === 'dueDate[]') {
                // The new due date field is just added
                // Create a new date picker
                data.element
                    .parent()
                    .datepicker({
                        format: 'yyyy/mm/dd'
                    })
                    .on('changeDate', function(evt) {
                        // Revalidate the date field
                        $('#taskForm').formValidation('revalidateField', data.element);
                    });
            }
        })

        // Add button click handler
        .on('click', '.addButton', function() {
            var $template = $('#taskTemplate'),
                $clone    = $template
                                .clone()
                                .removeClass('hide')
                                .removeAttr('id')
                                .insertBefore($template);

            // Add new fields
            // Note that we DO NOT need to pass the set of validators
            // because the new field has the same name with the original one
            // which its validators are already set
            $('#taskForm')
                .formValidation('addField', $clone.find('[name="task[]"]'))
                .formValidation('addField', $clone.find('[name="dueDate[]"]'))
        })

        // Remove button click handler
        .on('click', '.removeButton', function() {
            var $row = $(this).closest('.form-group');

            // Remove fields
            $('#taskForm')
                .formValidation('removeField', $row.find('[name="task[]"]'))
                .formValidation('removeField', $row.find('[name="dueDate[]"]'));

            // Remove element containing the fields
            $row.remove();
        });
});
</script>

Related examples