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

Asking fields to be unique

Examples

Sometime the user need to fill multiple fields in form, also each of them must be unique. Any of them has to be different to the remaining. You might think of using the different validator which requires two fields to be different to each other, such as an username and password shouldn't be the same. In our case, the different validator isn't useful because the number of fields for doing comparison are unknown.

The form in this example asks user to provide some email addresses. User is also required to fill at least one email address. Each of them, if present, must be unique. This kind of form can be seen in a lot of forms nowadays.

The approach illustrated in this example is that:

  • Use the emailAddress validator to ensure each field need to be a valid email address
  • Use the callback validator to check if the list of email addresses consist duplicated item
  • If all fields pass these validators, we then use the updateStatus() method to set them as valid fields

That are straight forward steps. The next sections show the implementation in details.

Checking duplicate items in array

In order to check whether an array contains duplicated items or not, we can use the easy way suggested by Rhett Anderson:

function hasDuplicatedItems(inputArray) {
    var obj              = {},
        numItems         = inputArray.length,
        duplicateRemoved = [];

    for (var i = 0; i < numItems; i++) {
        obj[inputArray[i]] = 0;
    }

    for (i in obj) {
        duplicateRemoved.push(obj[i]);
    }

    return duplicateRemoved.length === numItems;
}
For more information, you can read the Rhett Anderson's original post Eliminating Duplicates

Since the array of email addresses might consist of empty item, we need to adjust the code above a little bit to ensure that the array has at least one not-empty item and doesn't contain any duplicated items:

// Assume that $emails are the list of email elements
var numEmails        = $emails.length,
    notEmptyCount    = 0,
    obj              = {},
    duplicateRemoved = [];

for (var i = 0; i < numEmails; i++) {
    var v = $emails.eq(i).val();
    if (v !== '') {
        obj[v] = 0;
        notEmptyCount++;
    }
}

for (i in obj) {
    duplicateRemoved.push(obj[i]);
}

if (duplicateRemoved.length === 0) {
    // All the items are empty
} else if (duplicateRemoved.length !== notEmptyCount) {
    // The list of emails have duplicated items
}

Using the same names

The example code below demonstrates the implementation when all email fields use the same names, email[], for example.

It also uses the dynamic message feature that allows to show a different message depending on a particular condition:

$('#profileForm').formValidation({
    fields: {
        'email[]': {
            err: '#messageContainer',
            validators: {
                emailAddress: {
                    message: 'The value is not a valid email address'
                },
                callback: {
                    callback: function(value, validator, $field) {
                        ...

                        if (duplicateRemoved.length === 0) {
                            return {
                                valid: false,
                                message: 'You must fill at least one email address'
                            };
                        } else if (duplicateRemoved.length !== notEmptyCount) {
                            return {
                                valid: false,
                                message: 'The email address must be unique'
                            };
                        }

                        // Set all fields as valid
                        validator.updateStatus('email[]', validator.STATUS_VALID, 'callback');
                        return true;
                    }
                }
            }
        }
    }
})

The code also shows an usage of the err option which displays the message at the given container. This option also ensures that all the messages are shown once in case we use the same name for fields. If this option is omitted, then you will see different messages for each of invalid field.

<form id="profileForm" method="post" class="form-horizontal">
    <div class="form-group">
        <label class="col-xs-3 control-label">Emails</label>
        <div class="col-xs-5">
            <input type="text" class="form-control" name="email[]" />
        </div>
    </div>

    <div class="form-group">
        <div class="col-xs-5 col-xs-offset-3">
            <input type="text" class="form-control" name="email[]" />
        </div>
    </div>

    <div class="form-group">
        <div class="col-xs-5 col-xs-offset-3">
            <input type="text" class="form-control" name="email[]" />
        </div>
    </div>

    <div class="form-group">
        <div class="col-xs-5 col-xs-offset-3">
            <input type="text" class="form-control" name="email[]" />
        </div>
    </div>

    <div class="form-group">
        <div class="col-xs-5 col-xs-offset-3">
            <input type="text" class="form-control" name="email[]" />
        </div>
    </div>

    <!-- Message container -->
    <div class="form-group">
        <div class="col-xs-9 col-xs-offset-3">
            <div id="messageContainer"></div>
        </div>
    </div>

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

<script>
$(document).ready(function() {
    $('#profileForm').formValidation({
        framework: 'bootstrap',
        icon: {
            valid: 'glyphicon glyphicon-ok',
            invalid: 'glyphicon glyphicon-remove',
            validating: 'glyphicon glyphicon-refresh'
        },
        fields: {
            'email[]': {
                err: '#messageContainer',
                validators: {
                    emailAddress: {
                        message: 'The value is not a valid email address'
                    },
                    callback: {
                        callback: function(value, validator, $field) {
                            var $emails          = validator.getFieldElements('email[]'),
                                numEmails        = $emails.length,
                                notEmptyCount    = 0,
                                obj              = {},
                                duplicateRemoved = [];

                            for (var i = 0; i < numEmails; i++) {
                                var v = $emails.eq(i).val();
                                if (v !== '') {
                                    obj[v] = 0;
                                    notEmptyCount++;
                                }
                            }

                            for (i in obj) {
                                duplicateRemoved.push(obj[i]);
                            }

                            if (duplicateRemoved.length === 0) {
                                return {
                                    valid: false,
                                    message: 'You must fill at least one email address'
                                };
                            } else if (duplicateRemoved.length !== notEmptyCount) {
                                return {
                                    valid: false,
                                    message: 'The email address must be unique'
                                };
                            }

                            validator.updateStatus('email[]', validator.STATUS_VALID, 'callback');
                            return true;
                        }
                    }
                }
            }
        }
    });
});
</script>

Using different names

What if the email fields have different names? How we can set the validator rules for them?

In this case, we can use a same CSS class for all email fields

<input type="text" class="form-control userEmail" name="user.email[0]" />
<input type="text" class="form-control userEmail" name="user.email[1]" />
<input type="text" class="form-control userEmail" name="user.email[2]" />
<input type="text" class="form-control userEmail" name="user.email[3]" />
<!-- and so forth -->

and then use the selector option to apply the same set of validation rules for them:

$('#profileForm').formValidation({
    fields: {
        emails: {
            // All email fields have .userEmail class
            selector: '.userEmail',
            err: '#messageContainer',
            validators: {
                ...
            }
        }
    }
});
<form id="profileForm" method="post" class="form-horizontal">
    <div class="form-group">
        <label class="col-xs-3 control-label">Emails</label>
        <div class="col-xs-5">
            <input type="text" class="form-control userEmail" name="user.email[0]" />
        </div>
    </div>

    <div class="form-group">
        <div class="col-xs-5 col-xs-offset-3">
            <input type="text" class="form-control userEmail" name="user.email[1]" />
        </div>
    </div>

    <div class="form-group">
        <div class="col-xs-5 col-xs-offset-3">
            <input type="text" class="form-control userEmail" name="user.email[2]" />
        </div>
    </div>

    <div class="form-group">
        <div class="col-xs-5 col-xs-offset-3">
            <input type="text" class="form-control userEmail" name="user.email[3]" />
        </div>
    </div>

    <div class="form-group">
        <div class="col-xs-5 col-xs-offset-3">
            <input type="text" class="form-control userEmail" name="user.email[4]" />
        </div>
    </div>

    <!-- Message container -->
    <div class="form-group">
        <div class="col-xs-9 col-xs-offset-3">
            <div id="messageContainer"></div>
        </div>
    </div>

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

<script>
$(document).ready(function() {
    $('#profileForm').formValidation({
        framework: 'bootstrap',
        icon: {
            valid: 'glyphicon glyphicon-ok',
            invalid: 'glyphicon glyphicon-remove',
            validating: 'glyphicon glyphicon-refresh'
        },
        fields: {
            emails: {
                // All email fields have .userEmail class
                selector: '.userEmail',
                err: '#messageContainer',
                validators: {
                    emailAddress: {
                        message: 'The value is not a valid email address'
                    },
                    callback: {
                        callback: function(value, validator, $field) {
                            var $emails          = validator.getFieldElements('emails'),
                                numEmails        = $emails.length,
                                notEmptyCount    = 0,
                                obj              = {},
                                duplicateRemoved = [];

                            for (var i = 0; i < numEmails; i++) {
                                var v = $emails.eq(i).val();
                                if (v !== '') {
                                    obj[v] = 0;
                                    notEmptyCount++;
                                }
                            }

                            for (i in obj) {
                                duplicateRemoved.push(obj[i]);
                            }

                            if (duplicateRemoved.length === 0) {
                                return {
                                    valid: false,
                                    message: 'You must fill at least one email address'
                                };
                            } else if (duplicateRemoved.length !== notEmptyCount) {
                                return {
                                    valid: false,
                                    message: 'The email address must be unique'
                                };
                            }

                            validator.updateStatus('emails', validator.STATUS_VALID, 'callback');
                            return true;
                        }
                    }
                }
            }
        }
    });
});
</script>

Supporting dynamic fields

The last section shows how to keep the code above working with dynamic fields. We can add or remove field by using the addField() and removeField() methods, respectively.

You can look at the Adding dynamic field example to see how these methods are used in action.

<form id="profileForm" method="post" class="form-horizontal">
    <div class="form-group">
        <label class="col-xs-3 control-label">Emails</label>
        <div class="col-xs-5">
            <input type="text" class="form-control" name="email[]" />
        </div>
        <div class="col-xs-4">
            <button type="button" class="btn btn-default addButton"><i class="fa fa-plus"></i></button>
        </div>
    </div>

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

    <!-- Message container -->
    <div class="form-group">
        <div class="col-xs-9 col-xs-offset-3">
            <div id="messageContainer"></div>
        </div>
    </div>

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

<script>
$(document).ready(function() {
    $('#profileForm')
        .formValidation({
            framework: 'bootstrap',
            icon: {
                valid: 'glyphicon glyphicon-ok',
                invalid: 'glyphicon glyphicon-remove',
                validating: 'glyphicon glyphicon-refresh'
            },
            fields: {
                'email[]': {
                    err: '#messageContainer',
                    validators: {
                        emailAddress: {
                            message: 'The value is not a valid email address'
                        },
                        callback: {
                            callback: function(value, validator, $field) {
                                var $emails          = validator.getFieldElements('email[]'),
                                    numEmails        = $emails.length,
                                    notEmptyCount    = 0,
                                    obj              = {},
                                    duplicateRemoved = [];

                                for (var i = 0; i < numEmails; i++) {
                                    var v = $emails.eq(i).val();
                                    if (v !== '') {
                                        obj[v] = 0;
                                        notEmptyCount++;
                                    }
                                }

                                for (i in obj) {
                                    duplicateRemoved.push(obj[i]);
                                }

                                if (duplicateRemoved.length === 0) {
                                    return {
                                        valid: false,
                                        message: 'You must fill at least one email address'
                                    };
                                } else if (duplicateRemoved.length !== notEmptyCount) {
                                    return {
                                        valid: false,
                                        message: 'The email address must be unique'
                                    };
                                }

                                validator.updateStatus('email[]', validator.STATUS_VALID, 'callback');
                                return true;
                            }
                        }
                    }
                }
            }
        })

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

            // Add new field
            $('#profileForm').formValidation('addField', $email);
        })

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

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

            // Remove field
            $('#profileForm').formValidation('removeField', $email);
        });
});
</script>

Related examples