ASP.NET MVC: Displaying Client and Server Side Validation Using qTip Tooltips


The ASP.NET MVC framework makes it very easy to do both client and server side validation out of the box. Using DataAnnotations on your model properties the framework can display errors to the user client side using jQuery validation or for more complex situations, model errors can be returned using server side validation. Here is an example of a model and corresponding error messages that are displayed to the user on offending fields.

With the [Required] DataAnnotation on the NickName property we get the error message “The Nick name field is required” if the user leaves it blank. Also, the framework realizes that the Age property is an integer and thus if the user enters a value other than a numeric value, it will display and error message.

The functionality is great but the way in which the error messages are displayed is not very aesthetically pleasing. Also, when fields are validated on the client side, if you haven’t built in spaces for the validation text, your form will jump all over the place to make room for the messages. In an effort to make things a little more pleasing to the eye and to avoid unnecessary form re-sizing I’m going to show how you can display both client and server side validation in tooltips using the jQuery plugin qTip. Our goal is to transfer the form displayed above into the following:

You can download the complete solution for this example here.

I will be building off of the example from my last post that showed how to use jQuery UI to build Ajax forms.

The first thing you need to do is download the qTip library, add them to your project, and add references to the jquery.qtip.min.js script and jquery.qtip.css style sheet in your _Layout.cshtml or MasterPage.aspx.

The displaying of client side validation errors is handled in the jquery.validate.unobtrusive.js onError method. We need to alter that method to display the tooltip with the validation message instead of showing the default label. I cannot take credit for figuring out this code. I actually am using the technique presented here with a minor tweak. Open up the jquery.validate.unobtrusive.js script and replace the onError method with the following:

function onError(error, inputElement) {  // 'this' is the form element        
    var container = $(this).find("[data-valmsg-for='" + inputElement[0].name + "']"),
    replace = $.parseJSON(container.attr("data-valmsg-replace")) !== false;

    // Remove the following line so the default validation messages are not displayed        
    // container.removeClass("field-validation-valid").addClass("field-validation-error");

    error.data("unobtrusiveContainer", container);

    if (replace) {
        container.empty();
        error.removeClass("input-validation-error").appendTo(container);
    }
    else {
        error.hide();
    }

    /**** Added code to display the error message in a qTip tooltip ****/        
    // Set positioning based on the elements position in the form
    var elem = $(inputElement),
        corners = ['left center', 'right center'],
        flipIt = elem.parents('span.right').length > 0;

    // Check we have a valid error message
    if (!error.is(':empty')) {
        // Apply the tooltip only if it isn't valid
        elem.filter(':not(.valid)').qtip({
            overwrite: false,
            content: error,
            position: {
                my: corners[flipIt ? 0 : 1],
                at: corners[flipIt ? 1 : 0],
                viewport: $(window)
            },                
            show: {
                event: false,
                ready: true
            },
            hide: false,
            style: {
                classes: 'ui-tooltip-red' // Make it red... the classic error colour!
            }
        })

        // If we have a tooltip on this element already, just update its content
        .qtip('option', 'content.text', error);
    }

    // If the error is empty, remove the qTip
    else { elem.qtip('destroy'); }
}

Take a look at the qTip documentation for more information on what each of the options are doing here.

Your site is probably referencing the jquery.validate.unobtrusive.min.js file so make sure you replace that reference with the non-minified version you just updated.

Next, we need to update the DialogForm.js script file we created to do two things when the dialog window is closed; remove all the qTip tooltips and remove the form from the page after it is submitted. It turns out that when closing a jQuery UI dialog window, the constructed elements are not actually removed from the page but rather just hidden. After a successful ajax post and reload of the form, there will be issues rendering the server side validation messages if we don’t remove the submitted form. That is why the form has to be removed when the dialog is closed.

To make these changes, open the DialogForm.js file and add the close function to the dialog generation function.

$(function () {
    // Wire up the click event of any dialog links
    $('.dialogLink').live('click', function () {
        var element = $(this);

        // Retrieve values from the HTML5 data attributes of the link        
        var dialogTitle = element.attr('data-dialog-title');
        var updateTargetId = '#' + element.attr('data-update-target-id');
        var updateUrl = element.attr('data-update-url');
        
        // Generate a unique id for the dialog div
        var dialogId = 'uniqueName-' + Math.floor(Math.random() * 1000)
        var dialogDiv = "<div id='" + dialogId + "'></div>";

        // Load the form into the dialog div
        $(dialogDiv).load(this.href, function () {
            $(this).dialog({
                modal: true,
                resizable: false,
                title: dialogTitle,
                buttons: {
                    "Save": function () {
                        // Manually submit the form                        
                        var form = $('form', this);
                        $(form).submit();
                    },
                    "Cancel": function () {
                        $(this).dialog('close');
                    }
                },
                // **** START NEW CODE ****
                close: function () {
                    // Remove all qTip tooltips
                    $('.qtip').remove();

                    // It turns out that closing a jQuery UI dialog
                    // does not actually remove the element from the
                    // page but just hides it. For the server side 
                    // validation tooltips to show up you need to
                    // remove the original form the page
                    $('#' + dialogId).remove();
                }
                // **** END NEW CODE ****
            });

            // Enable client side validation
            $.validator.unobtrusive.parse(this);

            // Setup the ajax submit logic
            wireUpForm(this, updateTargetId, updateUrl);
        });
        return false;
    });
});

If we run the application at this point then the client side validation messages will be displayed in the qTip tooltips. Now, to display the server side validation messages in qTip tooltips as well.

To demonstrate how to do this I am going to create a custom validation attribute named AgeValidation and add it to our Profile model. This validation can actually be done client side but I want to show how to show the tooltips after a server side validation error, so humor me.

public class Profile
{
    [Required]
    public string Name { get; set; }

    [Required]
    [StringLength(10, MinimumLength=3)]
    [Display(Name="Nick name")]
    public string NickName { get; set; }

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

    [Required]
    [AgeValidation(ErrorMessage="You must be older than 12 to sign up")]
    public int Age { get; set; }
}

// I know this can be accomplished using the Range validation
// attribute but I have implmented it as a custom validation
// attribute to show how server side validation error messages
// can be displayed using qTip tooltips
public class AgeValidation : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        if (value == null)
            return false;

        int intValue;
        if (!int.TryParse(value.ToString(), out intValue))
            return false;

        return (intValue > 12);
    }
}

Now if the user attempts to submit a Profile model with an age less than 12, a server side validation error message will be added. The trick to displaying the server side validation errors in qTip tooltips is to know how ASP.NET MVC renders the default label with the error message. It turns out that when a validation error message is displayed, ASP.NET MVC creates a span element with the class ‘field-validation-error’ and its contents contain the error message. So, in order to display that message in a tooltip, all we need to do is extract that error message from the span element and load it into a tootip. This can be accomplished by the following javascript function:

$(function () {
    // Run this function for all validation error messages
    $('.field-validation-error').each(function () {
        // Get the name of the element the error message is intended for
        // Note: ASP.NET MVC replaces the '[', ']', and '.' characters with an
        // underscore but the data-valmsg-for value will have the original characters
        var inputElem = '#' + $(this).attr('data-valmsg-for').replace('.', '_').replace('[', '_').replace(']', '_');

        var corners = ['left center', 'right center'];
        var flipIt = $(inputElem).parents('span.right').length > 0;

        // Hide the default validation error
        $(this).hide();

        // Show the validation error using qTip
        $(inputElem).filter(':not(.valid)').qtip({
            content: { text: $(this).text() }, // Set the content to be the error message
            position: {            
                my: corners[flipIt ? 0 : 1],
                at: corners[flipIt ? 1 : 0],
                viewport: $(window)
            },
            show: { ready: true },
            hide: false,
            style: { classes: 'ui-tooltip-red' }
        });       
    });
});    

And lastly, we need to add a reference to the above script on the page that will display the form fields for the Profile model.

@model DialogFormExample.Models.Profile

@using (Html.BeginForm()) {    
        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)<br />
            @Html.ValidationMessageFor(model => model.Name)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.NickName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.NickName) <br />
            @Html.ValidationMessageFor(model => model.NickName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Email)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Email) <br />
            @Html.ValidationMessageFor(model => model.Email)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Age)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Age) <br />
            @Html.ValidationMessageFor(model => model.Age)
        </div>    
}
  
<script src="@Url.Content("~/Scripts/jquery.qtip.validation.js")" type="text/javascript" />

And that’s it. Now you have a form that will display both server and client side validation in qTip tooltips!

35 Responses to “ASP.NET MVC: Displaying Client and Server Side Validation Using qTip Tooltips”

  1. ASP.NET MVC: Displaying Client and Server Side Validation Using Error Icons « Nick Olsen's Programming Tips Says:

    [...] Server Side Validation Using Error Icons August 23, 2011 — Nick Olsen In a previous post I showed how you could display both client and server side validation using qTip tooltips. In this [...]

  2. Bruno Hunziker Says:

    Hi Nick

    Very nice code. Works almost perfectly for me. I have only one problem. When I scroll the side (in my case the left DIV of the page) up or down, the qTips don’t follow. Any Ideas? Someone else has the same Problem?

    Thanks again for this Post
    Bruno

    • Nick Olsen Says:

      Are you using this in the Dialog Form context? In the example code I supplied the tooltip should stick with the input even when scrolling. If you can supply an example project I could help out more as I can’t seem to replicate the issue. You also might want to check out the adjust method property as discussed in the documentation here: http://craigsworks.com/projects/qtip2/docs/position/#adjust.method

  3. sarah Says:

    how can I adjust the tooltip position?

    • Nick Olsen Says:

      Check out the Position options documentation here: http://craigsworks.com/projects/qtip2/docs/position/

  4. Peter Says:

    Great article! Just what I was looking for. However I’m running into an issue with the server validation script.

    I’m using JQuery 1.6.4 and the qtip server validate script is throwing an error on this line:

    var inputElem = ‘#’ + $(this).attr(‘data-valmsg-for’).replace(‘.’, ‘_’).replace(‘[', '_').replace(']‘, ‘_’);

    SCRIPT5007: Unable to get value of the property ‘replace’: object is null or undefined

    Any ideas on why this is happening and how to fix it?

    Thank you

  5. Peter Says:

    My bad, to test server validation, I turned off client validation so mvc was not emitting the attributes. Please disregard previous error.

    Thanks

  6. heoque Says:

    well done !

  7. heoque Says:

    hi again !
    to me to remove the flickering error message generated by MVC i just commented out this line of code (in onError function) :
    //error.removeClass(“input-validation-error”).appendTo(container);

    there is no need to append the element. all we need from the container is the error message.

    thanks again for the awesome work !

    • Nick Olsen Says:

      Thanks for the suggestion! The flickering is actually only an issue in Firefox for some reason. I will have to try it out.

  8. gokhan Says:

    DialogForm.js line 3, $.ajaxSetup({ cache: false }); instead of $.ajaxSetup({ cache: false; }); It caused to go directly /Home/EditProfile for me.

    Thanks for the post well done!

    • Nick Olsen Says:

      Sorry about that. That was pointed out previously and I updated the sample project a couple of days ago. When did you download the sample project?

      • gokhan Says:

        I got a few days ago, but I tried just now

  9. James S. Says:

    Thanks for the help with this! I have about the same exact thing working on mine, I just ended up using your script for the qTip in the js unobtrusive to get it working correctly… I do have one question however- have you ever done this and seen that the tooltip’s tip doesn’t get created? My validation errors are just the square, basically docked onto the input fields. I would love for some help on this one!

  10. Catcher Says:

    hi Nick,
    How do you test the server side validation?

    If I turn off client validation ,my server side validation not working.
    The html it rendered is as below:
    This field is required!

    no “data-valmsg-for” attribute.

  11. Joshua Arzt Says:

    Nick, this is really nice. Great job.

  12. Peter Says:

    Anyway to get this working with the latest version of qtip? I noticed their are some api changes for jquery.qtip-1.0.0-rc3.min.js. Below is my attempt to make it work. But its not working. For one thing, the qtip css is not needed with this version, just the js file. Any ideas? Thanks in advance! :)

    Jquery validation file, onError:

    elem.filter(‘:not(.valid)’).qtip({
    overwrite: false,
    content: error,
    position: {
    corner: {
    target:[flipIt ? 1 : 0],
    tooltip:[flipIt ? 0 : 1]
    }
    },
    show: {
    event: false,
    ready: true
    },
    hide: { delay:0},
    style: {
    name: ‘dark’ // Make it red… the classic error colour!
    }
    })

    • Nick Olsen Says:

      I actually built this against the 2.0 version of qTip (even though it isn’t fully released for production use). Here is an example of a working qtip that I’m using in another place:

      $(self).qtip({
      content: {
      text: ‘Some message here’
      },
      show: { event: false, ready: true },
      hide: false,
      position: {
      my: ‘center left’,
      at: ‘right center’
      },
      style: {
      tip: true,
      classes: ‘ui-tooltip-red’
      }}
      });

  13. Peter Says:

    Nick, thanks, but unless the qtip 2.0 library has backwards comparability, I don’t understand how the above example would work as the 2.0 API has changed. For example the position no longer uses ‘my and ‘at’ rather it introduces a new property called corner which takes an array of sub properties like target and tooltip. Also, the style has changed to a single word and no longer requires the inclusion of the .css file.

    Can you please provide how your onError looks with qtip 2.0? I would like to migrate over to 2.0, but each time I try, I break the original code per your original posting. (a great posting by the way :) )

    Many Thanks for your continued support!

    • Nick Olsen Says:

      I think you might have it backwards. qTip 1.0 used the corner property for positioning (http://craigsworks.com/projects/qtip/docs/tutorials/#position) and qTip 2.0 uses the jQuery UI position plugin with the ‘my’ and ‘at’ properties (http://craigsworks.com/projects/qtip2/docs/position/). The code supplied in this post is what is being used with qTip 2.0.

      Hope that helps.

      • Peter Says:

        Thanks Nick. I stand corrected. My apologies for wasting your time. :)
        Thanks again for the quick response.

  14. Jean-Fran├žois Beauchamp Says:

    Great tutorial!

    A question though: Any reason why you are not including jquery.qtip.validation.js in _Layout.cshtml?

    Cheers!

    JF

    • Nick Olsen Says:

      That is usually where I put it but I included it on that page just to make sure people where aware the file was needed.

  15. bmccleary Says:

    Nick,
    Have you looked in to ways that the onError method can be overriden so that we don’t have to modify the standard jquery.validate.unobtrusive.js file? There is a post at “http://forums.asp.net/t/1759887.aspx/1″ (scroll to the bottom for the answer) that describes this, but I don’t want spend a bunch of time trying this out if you have already determined that it’s not an option. I just wanted to get your thoughts on it before I delved in.

    • Nick Olsen Says:

      I do believe that is another way to do it. I haven’t done it myself but I’m pretty sure it is possible and to be honest is probably a better way to do it.

    • Leniel Macaferi Says:

      bmccleary,

      You can override it without touching jQuery original files. Do this:

      if ($(‘form’).length != 0)
      {
      var settngs = $.data($(‘form’)[0], ‘validator’).settings;

      settngs.errorPlacement = function(error, inputElement)
      {
      . . .

  16. Leniel Macaferi Says:

    Hi Nick,

    Great post. I borrowed the concepts and it’s working as expect from both Client and Server side.

    qTip is really a beautiful option to show validation errors… highly customizable!

    Keep posting my friend. That’s how we learn from each other…

    Warm regards from Brazil,

    Leniel

  17. jeff00seattle Says:

    Great posting
    I tried to do the same with qTip, but the conflicting presentation of “field-validation-error” was messing me up.

    Question: What would I need to do to present the qTips to the right of an invalid field?

    Thanks from Seattle
    Jeff

    • Nick Olsen Says:

      Take a look at the position option in the documentation here http://craigsworks.com/projects/qtip2/docs/position/. It uses the jQuery position plugin.

      • jeff00seattle Says:

        Thanks for the reply
        I tried forcefully modifying the position attributes within jquery.qtip.validation.js (replacing the flip array with a position option), but it did not change the position of the qtip.

        For example… my: ‘top center’, at: ‘bottom center’

        Is there something else I need to change to make this work?

  18. ASP.NET MVC Validation Using qTip jQuery Plugin | Easy jQuery | Free Popular Tips Tricks Plugins API Javascript and Themes Says:

    [...] is a blog post that explains how to do this in detail. Tagged: asp.net-mvc, asp.net-mvc-3, jquery, [...]

  19. Iulian Says:

    Hy,
    Very nice article !
    One observation :
    The .replace() used in this statement only replace the first occurences of ‘.’ ‘[' and ']‘
    var inputElem = ‘#’ + $(this).attr(‘data-valmsg-for’).replace(‘.’, ‘_’).replace(‘[', '_').replace(']‘, ‘_’);

    It should be something like :
    var inputElem = ‘#’ + $(this).attr(‘data-valmsg-for’).replace(/\./g,’_’).replace(/[\[\]]/g, ‘_’);

  20. Ankit PRAJAPATI Says:

    Hi,
    I implemented your Nice Tutorial, but there is one problem.
    Flickering is coming. First the MVC default validation message appears and then the Tooltip.
    I even commented the line “error.removeClass(“input-validation-error”).appendTo(container);”
    But still the flickering persists.
    Please help !!!!!!
    Thanx…..

    • Ankit PRAJAPATI Says:

      The Flickering occurs once only when the first time the validation occurs. After that it works smoothly.
      This behaviour is same for IE, Chrome and Opera.

  21. Login form still posting even though required input is empty | Stackforum Says:

    […] http://nickstips.wordpress.com/2011/08/18/asp-net-mvc-displaying-client-and-server-side-validation-u&#8230; […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 66 other followers

%d bloggers like this: