3

I have been looking at the following site - http://bootstrapvalidator.com/getting-started/#release

and I like the validation. I am developing an MVC 5 application and I am using bootstrap. I am currently using Decorators on my models to fire Validation.

e.e

[Required]
public string FirstName { get; set; }

My Markup then is as below:

    <div class="col-xs-5">
        @Html.TextBoxFor(model => model.FirstName, new { @class = "form-control input-sm" })
        @Html.ValidationMessageFor(model => model.FirstName)
    </div>

Another field on model would be for e.g

Age

[Range(0,150), ErrorMessage="Age field cannot be greater than 150"]
public int Age { get; set; }

So I would want that Error Message for Age injected into the toottip rather than repeating Validations server side and client side for every page in my Application

So if user trys to submit the form without FirstName filled in the validation message will fire.

What I would really like to achieve is something like this - http://bootstrapvalidator.com/settings/#form-container - so the field fires validation and the error message is put in ToolTip - has anyone done anything like this with MVC and is there a way we could force the Server Side Validation message to be injected into the error that would appear in that ToolTip?

Update

Attempting to use @Tieson answer below - not working currently. I added the styles just on the page that contain a percentage field that can only contain 3 numeric digits. My bundle config after downloading the bootstrap unobtrusice extension is as:

        var thirdPartyScripts = new ScriptBundle("~/bundles/thirdpartyscripts").Include(
            "~/Scripts/modernizr-*",
            "~/Scripts/jquery-{version}.js",
            "~/Scripts/jquery.validate.js",
            "~/Scripts/jquery.validate.unobtrusive.js",
            "~/Scripts/jquery.form.js",
            "~/Scripts/jquery.blockUI.js",
            "~/Scripts/jquery.autosize.min.js",
            "~/Scripts/bootstrap.js",
            "~/Scripts/jquery.validate.unobtrusive.bootstrap.js",
            "~/Scripts/respond.js",
            "~/Scripts/kendo/kendo.web.min.js",
            "~/Scripts/kendo/kendo.aspnetmvc.min.js",
            "~/Content/ckeditor/ckeditor.js",
            "~/Content/ckeditor/adapters/jquery.js",
            "~/Scripts/knockout-3.1.0.js",
            "~/Scripts/knockout.mapping-latest.js");

        thirdPartyScripts.Orderer = new AsIsBundleOrderer();
        bundles.Add(thirdPartyScripts);

The percentage field in my view model is as:

 [RegularExpression(@"^(?:999|[1-9]?[0-9]?[0-9])$", ErrorMessage = "Does this work")]
        public int Percentage{ get; set; }

I added the update to the bootstrap.js file and then in the document.ready function I added the update to the setDefaults validation. What I am seeing is that if I enter valid numbers to the field eg 50 it does not highlight green. If I enter 4 digits and tab away from field it does highlight red but there is no X icon included in the field that will render the tooltip error message and then when I enter valid data again it does not turn back from red. I put an alert('here') in the showErrors function in the setDefaults on the validator in document.ready and I do not see it firing the alert?

The rendered html on screen when I enter invalid data and the field goes red is:

<input class="form-control input-validation-error text-danger" data-val="true" data-val-regex="Does this work" data-val-regex-pattern="^(?:999|[1-9]?[0-9]?[0-9])$" id="txtPercentage" name="Percentage" placeholder="Percentage" type="text" value="" aria-invalid="true">

My cshtml markup is as below:

            <div class="row form-group">
                @Html.LabelFor(m => m.Percentage, new { @class = "col-md-3 control-label" })
                <div class="col-md-9">
                    @Html.TextBoxFor(m => m.Percentage, new { @class = "form-control", @placeholder = "Percentage", id = "txtPercentage" })
                </div>
            </div>

Do I need to add tootip to my markup here as well?

Ctrl_Alt_Defeat
  • 3,933
  • 12
  • 66
  • 116
  • Have you included the Validator js Library? If yes, then you have to know what the HTML ID of your form is. You can set this in the @Html.BeginForm(), then at the end of your page: $('#YourFormID').bootstrapValidator(); Related to your Custom Error Messages: You can use the data Attributes from HTML5, to display the messages you want – Dawood Awan Aug 22 '14 at 23:23
  • I suppose an option would be just write the js to validate the forms client side and still leave server side validation in place and render any further server side errors in a Validation Summary... – Ctrl_Alt_Defeat Aug 22 '14 at 23:24
  • Yes, it is always good practice to Validate your inputs on client side and on the Server Side, because if someone is good enough with JavaScript and they want to harm your website they can skip past Client side validation using FireBug. – Dawood Awan Aug 22 '14 at 23:26
  • @DawoodAwan - yeah or they could just turn off js in the browser :) - I guess what I am wanting to avoid is having to repeat validation in js and server side - so for Age Field for e.g I am trying to see if there is a way I can configure bootstrap validator to pick up this error message,? – Ctrl_Alt_Defeat Aug 22 '14 at 23:31
  • I am not that sure yet, but I think you could use @Html.ValidationMessageFor() http://stackoverflow.com/questions/19281415/how-to-use-html-validationmessagefor – Dawood Awan Aug 22 '14 at 23:36
  • I can't find a simple solution yet, but what I would do is Extend the Html helper Class, and add Bootstrap type Inputs, with the correct data attributes. So when the HTML is generated it will already have the data attributes for bootstrap (Instead of the current ones for jQueryUI). Then whenever I call the $(FORMId).bootstrapValidator() it would work without repeating my code. – Dawood Awan Aug 22 '14 at 23:55

3 Answers3

3

It's possible to rewrite Default settings of jQuery validator:

  <script type="text/javascript">
        $.validator.setDefaults({            
            showErrors: function (errorMap, errorList) {
                this.defaultShowErrors();

                // If an element is valid, it doesn't need a tooltip
                $("." + this.settings.validClass).tooltip("destroy");

                // Add tooltips
                for (var i = 0; i < errorList.length; i++) {
                    var error = errorList[i];
                    $(error.element).tooltip({ trigger: "focus" }) // Activate the tooltip on focus
                                    .attr("data-original-title", error.message);
                }
            }
     });
 </script>

Here, errorList gives us all of the not validated elements. By using its error.element property, we can attach a tooltip to this element.

VahidN
  • 18,457
  • 8
  • 73
  • 117
2

I've found that combining a few answers from SO let me use Bootstrap's tooltips in place of the inline validation messages, while letting me also use the styling of the .alert classes.

First, I tweaked the CSS from this answer: Modifying Twitter Bootstrap's Tooltip Colors Based on Position

/* INFO */
.tooltip.tooltip-info .tooltip-inner
{
    color: #31708f;
    background-color: #d9edf7;
    border-color: #bce8f1;
    background-image: -webkit-linear-gradient(top, #d9edf7 0, #b9def0 100%);
    background-image: linear-gradient(to bottom, #d9edf7 0, #b9def0 100%);
    background-repeat: repeat-x;
    border-color: #9acfea;
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
}

.tooltip.tooltip-info.top .tooltip-arrow,
.tooltip.tooltip-info.top-left .tooltip-arrow,
.tooltip.tooltip-info.top-right .tooltip-arrow
{
    border-top-color: #bce8f1;
}

.tooltip.tooltip-info.bottom .tooltip-arrow,
.tooltip.tooltip-info.bottom-left .tooltip-arrow,
.tooltip.tooltip-info.bottom-right .tooltip-arrow
{
    border-bottom-color: #bce8f1;
}

.tooltip.tooltip-info.right .tooltip-arrow
{
    border-right-color: #bce8f1;
}

.tooltip.tooltip-info.left .tooltip-arrow
{
    border-left-color: #bce8f1;
}


/* DANGER (ERROR) */
.tooltip.tooltip-danger .tooltip-inner
{
    color: #a94442;
    background-color: #f2dede;
    border-color: #ebccd1;
    background-image: -webkit-linear-gradient(top, #f2dede 0, #e7c3c3 100%);
    background-image: linear-gradient(to bottom, #f2dede 0, #e7c3c3 100%);
    background-repeat: repeat-x;
    border-color: #dca7a7;
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
}

.tooltip.tooltip-danger.top .tooltip-arrow,
.tooltip.tooltip-danger.top-left .tooltip-arrow,
.tooltip.tooltip-danger.top-right .tooltip-arrow
{
    border-top-color: #ebccd1;
}

.tooltip.tooltip-danger.bottom .tooltip-arrow,
.tooltip.tooltip-danger.bottom-left .tooltip-arrow,
.tooltip.tooltip-danger.bottom-right .tooltip-arrow
{
    border-bottom-color: #ebccd1;
}

.tooltip.tooltip-danger.right .tooltip-arrow
{
    border-right-color: #ebccd1;
}

.tooltip.tooltip-danger.left .tooltip-arrow
{
    border-left-color: #ebccd1;
}


/* WARNING */
.tooltip.tooltip-warning .tooltip-inner
{
    color: #8a6d3b;
    background-color: #fcf8e3;
    border-color: #faebcc;
    background-image: -webkit-linear-gradient(top, #fcf8e3 0, #f8efc0 100%);
    background-image: linear-gradient(to bottom, #fcf8e3 0, #f8efc0 100%);
    background-repeat: repeat-x;
    border-color: #f5e79e;
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
}

.tooltip.tooltip-warning.top .tooltip-arrow,
.tooltip.tooltip-warning.top-left .tooltip-arrow,
.tooltip.tooltip-warning.top-right .tooltip-arrow
{
    border-top-color: #faebcc;
}

.tooltip.tooltip-warning.bottom .tooltip-arrow,
.tooltip.tooltip-warning.bottom-left .tooltip-arrow,
.tooltip.tooltip-warning.bottom-right .tooltip-arrow
{
    border-bottom-color: #faebcc;
}

.tooltip.tooltip-warning.right .tooltip-arrow
{
    border-right-color: #faebcc;
}

.tooltip.tooltip-warning.left .tooltip-arrow
{
    border-left-color: #faebcc;
}


/* SUCCESS */
.tooltip.tooltip-success .tooltip-inner
{
    color: #3c763d;
    background-color: #dff0d8;
    border-color: #d6e9c6;
    background-image: -webkit-linear-gradient(top, #dff0d8 0, #c8e5bc 100%);
    background-image: linear-gradient(to bottom, #dff0d8 0, #c8e5bc 100%);
    background-repeat: repeat-x;
    border-color: #b2dba1;
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
}

.tooltip.tooltip-success.top .tooltip-arrow,
.tooltip.tooltip-success.top-left .tooltip-arrow,
.tooltip.tooltip-success.top-right .tooltip-arrow
{
    border-top-color: #d6e9c6;
}

.tooltip.tooltip-success.bottom .tooltip-arrow,
.tooltip.tooltip-success.bottom-left .tooltip-arrow,
.tooltip.tooltip-success.bottom-right .tooltip-arrow
{
    border-bottom-color: #d6e9c6;
}

.tooltip.tooltip-success.right .tooltip-arrow
{
    border-right-color: #d6e9c6;
}

.tooltip.tooltip-success.left .tooltip-arrow
{
    border-left-color: #d6e9c6;
}

Then we add the Bootstrap Unobtrusive Validation plugin: https://www.nuget.org/packages/jquery.validate.unobtrusive.bootstrap/ - this extends the Unobtrusive Validation plugin such that it adds the .has-error, etc. classes to the nearest .form-group of the triggering input.

The one part that bugs me is this; I had to modify the bootstrap.js file a bit, from this:

  $tip
    .detach()
    .css({ top: 0, left: 0, display: 'block' })
    .addClass(placement)

to this:

  $tip
    .detach()
    .css({ top: 0, left: 0, display: 'block' })
    .addClass(placement)
    .addClass(this.$element.data('tooltip'))

which lets the tooltip plugin read the data- attribute which defines which tooltip style to apply.

Lastly, modify the validator settings a bit:

$.validator.setDefaults({
    showErrors: function (errorMap, errorList) {
        this.defaultShowErrors();

        // destroy tooltips on valid elements                              
        $("." + this.settings.validClass).tooltip("destroy");

        // add/update tooltips 
        for (var i = 0; i < errorList.length; i++) {
            var error = errorList[i];

            var id = '#' + error.element.id;
            var isInModal = $(id).parents('.modal').length > 0;

            $(id)
                .tooltip({ trigger: 'focus', placement: isInModal ? 'auto right' : 'right', container: isInModal ? false : '.row', html: true })
                .attr('data-tooltip', 'tooltip-danger')
                .attr('data-original-title', error.message);
        }
    }
});

The version I use is a little different from what @VahidN suggested; I sometimes use modals to display input forms, and applying a container to the tooltip doesn't render correctly in a modal. The container is necessary if your inputs might be used in an .input-group, though, so I just use the closest .row.

Community
  • 1
  • 1
Tieson T.
  • 20,774
  • 6
  • 77
  • 92
  • I also have form inputs in a modal window so will give your solution above a go. Do you still use the Html.ValidationMessageFor in your cshtml anywere? – Ctrl_Alt_Defeat Aug 23 '14 at 13:19
  • does the $.validator.setDefaults go in document.ready on the page that I want this to work on? For some reason I am not getting the error into a tooltip in the form field - it isnt appearing at all on the screen - the field goes red if I enter incorrect data - but not green if the data is valid - I will update question above with what I am seeing – Ctrl_Alt_Defeat Aug 24 '14 at 18:03
  • I have it in a separate file. Doesn't need to be in ready. Probably shouldn't be. Just make sure it's after all the validation plugins. – Tieson T. Aug 24 '14 at 18:06
  • updated my question - will try moving the js for validator.setDefaults to a seperate file and possibly include that in my bundle as well? – Ctrl_Alt_Defeat Aug 24 '14 at 18:20
  • Do I need to add a tootip to my cshtml markup? And any idea why the field doesnt highlight green when valid data is entered? – Ctrl_Alt_Defeat Aug 24 '14 at 18:39
  • The validator bit above actually appends the correct `data-` attribute, so no, you don't have to add the tooltip. Everything else just leverages the existing unobtrusive validation attributes. Not sure why the `.has-success` isn't being appended; looking into it. – Tieson T. Aug 24 '14 at 20:44
  • @Ctrl_Alt_Defeat Ah, looks like the bootstrap validation expects the validation message to exist, and is silently failing when it can't find it. I'll amend my answer when I figure out a work-around. – Tieson T. Aug 24 '14 at 21:06
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/59902/discussion-between-tieson-t-and-ctrl-alt-defeat). – Tieson T. Aug 24 '14 at 21:19
  • 1
    Simplest solution I've found is to "hide" the validation message, like this: . Not ideal, but it works. – Tieson T. Aug 24 '14 at 21:51
1

MVC framework uses JQuery validation. Razor view helpers generate HTML data annotations based on model property and it's attributes.

[Required(ErrorMessage="You must enter a username")]
public string Username {get; set;}

@Html.EditorFor(m => m.Username)
<input type="text" data-val="true" data-val-required="You must enter a username" value="">
</input>
<!--Generated placeholder for clientside validator message-->

You would have to overwrite editor helper(s) to make bootstrap validator specific data-* annotations. Here's an article to quickly introduce you to writing your own custom DisplayFor and EditorFor helpers:

http://buildstarted.com/2010/09/10/overriding-displayfor-and-editorfor-to-create-custom-outputs-for-mvc/