ASP.NET MVC: Displaying Client and Server Side Validation Using Error Icons


In a previous post I showed how you could display both client and server side validation using qTip tooltips. In this post I will show how you can display an error icon next to the field that is invalid and then when the user hovers over the icon, display the error message (demonstrated below).

As done previously I will be using the same example project from this post where we created a dialog form which was submitted via Ajax.

You can download the complete solution for this example here.

First, the error icon. I utilized the ui-icon-alert class that comes with jQuery UI to display the error icon. But, to get the icon to display correctly without having to create a containing div element around the icon, we need to add a new class to the jquery.ui.theme.css file. Open up the default jquery.ui.theme.css file or if you have added a custom theme, the jquery-ui-[version number].custom.css file and find the states and images sub section under the Icons section. Add the following css class to the list of classes there.

.ui-state-error-icon { display:inline-block;  width: 16px; height: 16px; background-image: url(images/ui-icons_cd0a0a_256x240.png);  }

This class will allow a 16 x 16px icon from the error images png to be displayed in an empty element.

Next we need to change the onError function in the jquery.validate.unobtrusive.js javascript file. Open that file and replace the onError function with that shown below.

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;

    container.removeClass("field-validation-valid").addClass("field-validation-error");
    error.data("unobtrusiveContainer", container);

    if (replace) {
            
        // Do not display the error message
        //container.empty();
        //error.removeClass("input-validation-error").appendTo(container);

        // If the error message is an empty string, remove the classes
        // from the container that displays the error icon.  Otherwise
        // Add the classes necessary to display the error icon and
        // wire up the qTip tooltip for the container
        if ($(error).text() == "") {
            container.removeClass("ui-state-error-icon").removeClass("ui-icon-alert");
        }
        else {
            container.addClass("ui-state-error-icon").addClass("ui-icon-alert");

            $(container).qtip({
                overwrite: true,
                content: $(error).text(),
                style: {
                    classes: 'ui-tooltip-red'
                }
            });
        }
    }
    else {
        error.hide();
    }
}

Here instead of displaying the error message in associated container, we are displaying the alert icon and wiring up a qTip tooltip to display the error text. (Make sure in your Layout or MasterPage that you reference the jquery.validate.unobtrusive.js javascript file not the jquery.validate.unobtrusive.min.js file.)

If you run the application now all client side errors will be displayed using little error icons as pictured above and if the user hovers over the icon, the error message will be displayed in the tooltip.

To make server side validation messages appear in the same way we need to add another javascript function to each page. Create a new javascript file named jquer.qtip.validation.js and paste the following code into it.

$(function () {
    // Run this function for all validation error messages
    $('.field-validation-error').each(function () {

        // Get the error text to be displayed
        var errorText = $(this).text();

        // Remove the text from the error message span
        // element and add the classes to display the icon
        $(this).empty();
        $(this).addClass("ui-state-error-icon").addClass("ui-icon-alert");

        // Wire up the tooltip to display the error message
        $(this).qtip({
            overwrite: true,
            content: errorText,
            style: {
                classes: 'ui-tooltip-red'
            }
        });     
    });
});    

Here we are doing the same thing we did for client side validation except we are iterating over all elements with the field-validation-error class and removing its text, displaying the icon, and placing the error message in the tooltip. Make sure that on every form where you have server side validation displayed that you reference the jquery.qtip.validation.js javascript file.

There you have it. Client and server side validation displayed using error icons and tooltips.

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!

ASP.NET MVC: Client Side Validation with an Ajax Loaded Form


In my last post I discussed how to perform some CRUD operations using Ajax and the jQuery UI dialog window. In that posted I included a little gem which I did not point out and probably should have.

The ASP.NET MVC framework provides client side validation using the jQuery validation library. It is a great tool for the user as they receive immediate notification when they have entered an invalid value but it is also great for the developer as you reduce unnecessary load on the server. The problem is all the client side validation logic is setup when the page is loaded so if you load a form using Ajax after the original page has been loaded, client side validation will not work for that form.

In order to remedy this all you need to do is make a manual call to the jQuery validation scripts passing in a selector that will include the form you load. Here is the line that needs to be included:


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

Above I have indicated that I want the jQuery validation library to parse all form elements but if you form has been given an id, you can pass in the id of the form as well. Below is a more complete example from my last post that shows the form being loaded into the dialog window and then the call to the jQuery validation library.

// Load the form into the dialog div
$(dialogId).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'); }
		}
	});

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