Fire jquery unobtrusive validation with bootstrap , Asp.net Mvc 3 and backbone
T

3

5

I tried to find the bits of information available on the net, But couldn't hook it up together. I have an application which makes use of a login page. Now I'm not binding the view to any model. However I have two inputs (Username and password). I want to fire the jquery unobtrusive validation feature that comes with Asp.net Mvc. Here are the samples:

Backbone Template with Twitter bootstrap which is the Login template (underscore templating engine).

@using (@Html.BeginForm("Login", "Home", FormMethod.Post, new { @class = "form-horizontal" }))
               {
               <div class="modal-body">
                   <div class="control-group">
                        <label class="control-label" for="inputEmail">Email</label>
                        <div class="controls">
                            @*<input type="text" id="inputEmail" placeholder="Email">*@
                            @Html.TextBox("Email", "", new { @placeholder = "Email" })
                        </div>
                    </div>
                    <div class="control-group">
                        <label class="control-label" for="inputPassword">Password</label>
                        <div class="controls">
                            <input type="password" id="inputPassword" placeholder="Password">
                        </div>
                    </div>
                    <div class="control-group">
                    @*<div class="controls">
                        <label class="checkbox">
                            <input type="checkbox"> Remember me
                        </label>
                        <button type="submit" class="btn">Sign in</button>
                    </div>*@
                </div>
             </div>
               <div class="modal-footer">
                    @*<a class="btn" href="#" id="closeBtn">Close</a>*@ <button type="submit" class="btn btn-primary" >Login</button>
               </div>
               }  

The Coffeescript for Login view

require ['backbone','bootstrap','jqValUnobtrusive'],(Backbone) ->   
class LoginView extends Backbone.View

    el:$("#content")        

    initialize: ->                      
        @render()
        @validationOptions = @options.validationOptions

    validationOptions:
        rules:
            'Email':
                required:true
            'user[password]':
                required:true
    close:->
        console.log "hello"
    render : ->     
        $(@el).find("#login").html _.template $("#loginTemplate").html() 

App = new LoginView

And My controller to add Server side logic to validation rule in absence of javascript from the browser

[HttpPost]
    [ValidateInput(true)]
    public ActionResult Login(FormCollection Form)
    {
        ViewBag.Message = "Method posted";
        if (Form["Email"] == string.Empty)
        {
            ModelState.AddModelError("Email", "Please Provide a Username");
            ModelState.SetModelValue("Email", ValueProvider.GetValue("Email"));

        }
        return View();
    }

Would someone be able to shine some light on this problem? How to put the twitter bootstrap validation messages with this jquery unobtrusive code to show the errors? Or extend the jquery unobtrusive validation plugin to work with twitter bootstrap / backbone to perform the validation?

My criteria is the username and password shouldn't be NULL or blank . A simple case for study for validating a form without model binding (I dont want to bind the Login page to any model). Rather hook up the jquery unobtrusive plugin with extending backbone and twitter bootstrap styling!

Thurman answered 11/10, 2012 at 7:29 Comment(5)
If you want the unobtrusive validation that is automatically generated by MVC, I think you need to use Data Annotations on Model objects which are bound to the view. This is the method I have used and can provide a sample. However, if you don't want to use a model, then you will have to write the jquery validation yourself, and it won't have anything to do with the MVC layer.Greenheart
@MiikaL. Yes precisely . But Thats what I don't want actually . There is a way to put unobtrusive validation even if we dont put model . What if I want to put or render a view if I am not tieing it to a particular table ? Like a login page ? I dont want the data annotations to be applied to all but only username and password field . in that case I would never get the login to pass the validation . So to avoid that I just rendered a view with two fields username and password and validate it via form collecitonThurman
Mmm.. I think your View should not be using any database object (directly) as a Model. Rather create a ViewModel object, which would then contain the data annotations relating to that object. For example, I have a database object User, but then I have a LoginModel object with data annotations ([Required]) which only contains username and password that gets passed to my LoginView and received back by the Controller.Greenheart
The above would also mean you don't need to write validation code in the controller, because you could just call ModelState.IsValid, and validate with the same annotations as used by the javascript.Greenheart
@MiikaL Okay thats fine now how do i trigger the bootstrap classes with these validations then ? I mean instead of jquery ui styles I must show the twitter bootstrap error messages and styling ?Thurman
G
9

Here is my method for replacing jquery error messages with bootstrap styles when using mvc unobtrusive validation:

 /*
 * Form Validation
 * This script will set Bootstrap error classes when form.submit is called.
 * The errors are produced by the MVC unobtrusive validation.
 */
 $(function () {
     $('form').submit(function () {
         $(this).find('div.control-group').each(function () {
             if ($(this).find('span.field-validation-error').length == 0) {
                 $(this).removeClass('error');
             }
         });

         if (!$(this).valid()) {
             $(this).find('div.control-group').each(function () {
                 if ($(this).find('span.field-validation-error').length > 0) {
                     $(this).addClass('error');
                 }
             });
         }
     });
     $('form').each(function () {
         $(this).find('div.control-group').each(function () {
             if ($(this).find('span.field-validation-error').length > 0) {
                 $(this).addClass('error');
             }
         });
     });
 });
 var page = function () {
     //Update that validator
     $.validator.setDefaults({
         highlight: function (element) {
             $(element).closest(".control-group").addClass("error");
         },
         unhighlight: function (element) {
             $(element).closest(".control-group").removeClass("error");
         }
     });
 } ();
 /* End Form Validation */

There is some talk in the comments for the question, but the tech behind this script is Asp.Net MVC ViewModel object decorated with Data Annotations ([Required] in my login case), which is passed to a View and displayed with the following (only included relevant bits unless more is required).

@using (Html.BeginForm())
{
    ...
    <div id="errors"></div>
    @Html.ValidationSummary(true, "Sign in was unsuccessful", new { @class = "alert alert-block alert-error" })
    <div id="laatikko" class="well">
        <div class="control-group">
        <label for="kayttajaTunnus" class="control-label">@Kaannokset.Yhteiset.Sähköposti</label>
        <div class="controls">
            @Html.TextBoxFor(m => m.kayttajaTunnus)
            @Html.ValidationMessageFor(m => m.kayttajaTunnus, null, new { @class = "help-inline" })
        </div>
    </div>
    <div class="control-group">
        <label for="salasana" class="control-label">@Kaannokset.Yhteiset.Salasana</label>
        <div class="controls">
            @Html.PasswordFor(m => m.salasana)
            @Html.ValidationMessageFor(m => m.salasana, null, new { @class = "help-inline error" })
        </div>
    </div>
    <input id="signIn" class="btn btn-primary" data-loading-text="Searching for user..." type="submit" value="Sign In" />
}
Greenheart answered 11/10, 2012 at 10:43 Comment(4)
I tried your solution its working well with postback from server . But its not doing the client side validation . Its just the way I wanted ofcourse but any ideas how validate it at client side ? I did all the required codes you have given but no client side validation . Only server sideThurman
You had data annotations on the View Model object? Does your web.config set ClientValidationEnabled and UnobtrusiveJavaScriptEnabled to true? Have you got a Html.BeginForm() and @Html.ValidationSummary() within it?Greenheart
@MiiikaL yes thanks . The issue was something different :) I got it resolved . :) so marked it as answer :)Thurman
All is sorted and working as intended then? Glad I could help.Greenheart
O
3

Miika's answer is spot on. The only thing I'm going to add to it is making it work for when the user actually corrects the errors on the page. To do, you'll need to override MVC's validation summary code to also remove the .error class from the .control-group. Here's Miika's code with this added to it:

$(function() {
    var validator = $('form').data('validator');
    var aspErrorPlacement = validator.settings.errorPlacement;
    validator.settings.errorPlacement = function (error, element) {
        aspErrorPlacement(error, element);
        if (error.length > 0 && error[0].innerHTML === "") {
            error.closest('div.control-group').removeClass('error');
        }
    };
    $('span.field-validation-valid, span.field-validation-error').each(function () {
        $(this).addClass('help-inline');
    });

    $('form').submit(function () {
        if ($(this).valid()) {
            $(this).find('div.control-group').each(function () {
                if ($(this).find('span.field-validation-error').length == 0) {
                    $(this).removeClass('error');
                }
            });
        } else {
            $(this).find('div.control-group').each(function () {
                var control = $(this).find('span.field-validation-error')
                if (control.length > 0) {
                    $(this).addClass('error');
                }
            });
        }
    });
});

This should do it for you.

Oeflein answered 12/10, 2012 at 22:1 Comment(1)
it does not work correctly, at least for me. For example in required field I input value, set focus to different field - required field goes red, I come back to it, add value, go to next field - error is removed, so far everything is ok, I come back to required field and remove value and go to next field - bam - error message appears, but it is black.Stereo
S
1

This place in ASP.NET MVC is really how to say... broken?

I've looked at proposed solutions and I haven't found one I like, so I wrote my own.

There are two problems:
1) you need to override how highlighting/unhighlighting is performed
2) ok, second problem is because of the first problem and it exists only if you're using jquery.validation.unobtrusive.js. Thing is, that you can't override highlighting/unhighlighting using setDefaults method in jquery.validate because of the way jquery.validation.unobtrusive.js works.

So solution is this:

(function ($) {
  $.fn.bootstrapifyValidation = function () {
    var caller = this;
    function boostrapHighlight(element) {
        $(element).closest(".control-group").addClass("error");
        $(element).trigger('highlated');
    };

    function boostrapUnhighlight(element) {
        $(element).closest(".control-group").removeClass("error");
        $(element).trigger('unhighlated');
    };

    caller.each(function (i, o) {
        var settings = $(o).data('validator').settings;
        settings.highlight = boostrapHighlight;
        settings.unhighlight = boostrapUnhighlight;
    });

    caller.find("div.validation-summary-errors").addClass("alert alert-block alert-error");
 }
})(jQuery)

$(function () {
   $('form').bootstrapifyValidation();
})
Stereo answered 11/7, 2013 at 13:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.