IE9 Bug: Div height grows on hover when overflow is set to auto


It turns out that if you have a div set to overflow:auto, the contents of that div are wider than the containing div, and you have an element with a :hover css class inside that div that IE9 will start messing with the height of the div whenever the mouse is moved inside it. (see below)

Image

 

The work around for this is quite easy.  All you need to do is specify a height on the wrapper div and set the inner div to a height of 100%.  You can see an example of the problem and the solution here.

 

Posted in Uncategorized. Tags: , , . 1 Comment »

ASP.NET MVC: LessThan and GreaterThan Validation Attributes


ASP.NET MVC ships with a handy Compare attribute that allows you to compare two inputs and display a validation message if they do not match. This is great for sign up pages where a password needs to be entered twice and we need to ensure that they are equal but aside from that, I haven’t found any other use for the Compare attribute. What I have found that I have needed more are LessThan and GreaterThan attributes that allow you to compare two inputs and ensure that one is less than or greater than the other. The ASP.NET MVC Framework doesn’t not come loaded with such attributes so using the Compare attribute as a template, I have created them.

You can download a project here that has the complete source code and test pages for these attributes.

We will start first with the validation attribute:

public class NumericLessThanAttribute : ValidationAttribute, IClientValidatable
{
    private const string lessThanErrorMessage = "{0} must be less than {1}.";
    private const string lessThanOrEqualToErrorMessage = "{0} must be less than or equal to {1}.";                

    public string OtherProperty { get; private set; }

    private bool allowEquality;

    public bool AllowEquality
    {
        get { return this.allowEquality; }
        set
        {
            this.allowEquality = value;
                
            // Set the error message based on whether or not
            // equality is allowed
            this.ErrorMessage = (value ? lessThanOrEqualToErrorMessage : lessThanErrorMessage);
        }
    }        

    public NumericLessThanAttribute(string otherProperty)
        : base(lessThanErrorMessage)
    {
        if (otherProperty == null) { throw new ArgumentNullException("otherProperty"); }
        this.OtherProperty = otherProperty;            
    }        

    public override string FormatErrorMessage(string name)
    {
        return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, this.OtherProperty);
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        PropertyInfo otherPropertyInfo = validationContext.ObjectType.GetProperty(OtherProperty);   
            
        if (otherPropertyInfo == null)
        {
            return new ValidationResult(String.Format(CultureInfo.CurrentCulture, "Could not find a property named {0}.", OtherProperty));
        }

        object otherPropertyValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);

        decimal decValue;
        decimal decOtherPropertyValue;

        // Check to ensure the validating property is numeric
        if (!decimal.TryParse(value.ToString(), out decValue))
        {
            return new ValidationResult(String.Format(CultureInfo.CurrentCulture, "{0} is not a numeric value.", validationContext.DisplayName));
        }

        // Check to ensure the other property is numeric
        if (!decimal.TryParse(otherPropertyValue.ToString(), out decOtherPropertyValue))
        {
            return new ValidationResult(String.Format(CultureInfo.CurrentCulture, "{0} is not a numeric value.", OtherProperty));
        }

        // Check for equality
        if (AllowEquality && decValue == decOtherPropertyValue)
        {
            return null;
        }
        // Check to see if the value is greater than the other property value
        else if (decValue > decOtherPropertyValue)
        {
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
        }            

        return null;
    }

    public static string FormatPropertyForClientValidation(string property)
    {
        if (property == null)
        {
            throw new ArgumentException("Value cannot be null or empty.", "property");
        }
        return "*." + property;
    }       

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {           
        yield return new ModelClientValidationNumericLessThanRule(FormatErrorMessage(metadata.DisplayName), FormatPropertyForClientValidation(this.OtherProperty), this.AllowEquality);
    }
}

Like the compare attribute, this attribute will compare against another value on the form. The other property name is passed into the attributes constructor. There is also another named parameter, AllowEquality, which allows you to specify whether or not the value being validated can be equal to the ‘other’ property. The IsValid method is pretty straightforward and compares the two values to see if they are valid.

The last method, GetClientValidationRules creates a ModelClientValidationNumericLessThanRule class defined below:

public class ModelClientValidationNumericLessThanRule : ModelClientValidationRule
{
    public ModelClientValidationNumericLessThanRule(string errorMessage, object other, bool allowEquality)
    {
        ErrorMessage = errorMessage;
        ValidationType = "numericlessthan";
        ValidationParameters["other"] = other;
        ValidationParameters["allowequality"] = allowEquality;
    }
}

This class specifies the client validation type and parameters that will be loaded into the data attributes of the input on the html page. Here we have specified that the jQuery client validation type has a name of ‘numericlessthan’ and that it will accept to parameters values named ‘other’ and ‘allowEquality’.

Now that we have created these two classes, we can now generate a model to test the validation:

public class NumericLessThanViewModel
{
    public decimal MaxValue { get; set; }

    [NumericLessThan("MaxValue", AllowEquality = true)]
    [Display(Name="Value")]
    public decimal Value { get; set; }
}

For this to work we need at least two properties on the model; one that specifies the maximum value and another that will be used for the user input. On the user input property, add the NumericLessThan attribute and specify the name of the ‘other’ property to which it will be compared and whether or not equality is allowed. The ‘other’ value will usually be loaded as a hidden field in the form.

At this point just the server side validation has been setup. We need to add a javascript file as well to enable client side validation.

jQuery.validator.addMethod('numericlessthan', function (value, element, params) {
    var otherValue = $(params.element).val();

    return isNaN(value) && isNaN(otherValue) || (params.allowequality === 'True' ? parseFloat(value) <= parseFloat(otherValue) : parseFloat(value) < parseFloat(otherValue));
}, '');

jQuery.validator.unobtrusive.adapters.add('numericlessthan', ['other', 'allowequality'], function (options) {
    var prefix = options.element.name.substr(0, options.element.name.lastIndexOf('.') + 1),
    other = options.params.other,
    fullOtherName = appendModelPrefix(other, prefix),
    element = $(options.form).find(':input[name=' + fullOtherName + ']')[0];

    options.rules['numericlessthan'] = { allowequality: options.params.allowequality, element: element };
    if (options.message) {
        options.messages['numericlessthan'] = options.message;
    }
});

function appendModelPrefix(value, prefix) {
    if (value.indexOf('*.') === 0) {
        value = value.replace('*.', prefix);
    }
    return value;
}

The first method in the code above adds the actual method that is called when validating the input. In it we check to see if both values are numbers and depending on whether or not we specified to allow equality, we check to ensure that the user input is less than or equal to the other value.

The second method adds the rule to the set of jQuery validation adapters and supplies the wiring up of the parameters that will be supplied to the validation method.

And that is it. The ASP.NET MVC framework and the jQuery validation libraries will take care of the rest.

In the downloadable project above I have included a NumericGreaterThan attribute as well but as you can image, the code is almost identical to the LessThan attribute so I will not be going over it here.

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: Internal Server Error (500) on Action Method Returning Json Result


The ASP.NET MVC framework allows you to easily return Json from an action method. This makes jQuery Ajax calls very easy to implement, as shown below.

The JavaScript

$(.button).getJSON('/Home/GetJsonData', 
                   { id = 34 },
                   function(data) {
                      // Do something with it
                   }
);

The Action Method

public ActionResult GetJsonData(int id)
{
     Person person = this.personService.GetPerson(id);     
     return Json(person);
}

There is only one problem with the above action method. If you attempt to run it, the call to the ActionMethod will result in a Internal Server Error (Error 500). The reason is that by default data can only be retrieved using a POST operation if your action method returns a Json result. To make this work with a GET request, all you need to use is the overloaded Json() method shown below.

public ActionResult GetJsonData(int id)
{
     Person person = this.personService.GetPerson(id);     
     return Json(person, JsonRequestBehavior.AllowGet);
}

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);
});

ASP.NET MVC: Ajax Dialog Form Using jQuery UI


In one of my recent projects I needed to display some information, allow the user to edit it utilizing a dialog window, post the updated information and reload it for the user using Ajax. I needed to perform the named operations for multiple models so I set out to create some generic code that could be reused over and over. Below is a picture of what I am trying to accomplish.

The project used for the post below can be downloaded here.

First, the model. For this example, we will use a simple model that contains some profile information for a user.

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]
    public int Age { get; set; }
}

Second, we need to create three action methods. One will be used to display the profile information, another to display the form for editing the profile, and lastly another that will be used to save the edited profile object. The first two should always return a PartialView as each of these action methods will be called using Ajax and their result will be loaded into div elements; the first into a div used to display the saved profile and the second into the edit dialog. The third action method will return a PartialView if the ModelState is invalid so that the errors can be displayed to the user and a Json result indicating the save was successful if all went well. (Note that in this example I am just storing the profile information in Session but obviously this would be stored in a database or some other data store.)

public ActionResult Profile()
{
    Profile profile = new Profile();

    // Retrieve the perviously saved Profile
    if (Session["Profile"] != null)
        profile = Session["Profile"] as Profile;

    return PartialView(profile);
}

public ActionResult EditProfile()
{
    Profile profile = new Profile();

    // Retrieve the perviously saved Profile
    if (Session["Profile"] != null)
        profile = Session["Profile"] as Profile;

    return PartialView(profile);
}

[HttpPost]
public ActionResult EditProfile(Profile profile)
{
    // If the ModelState is invalid then return
    // a PartialView passing in the Profile object
    // with the ModelState errors
    if (!ModelState.IsValid)
        return PartialView("EditProfile", profile);

    // Store the Profile object and return
    // a Json result indicating the Profile 
    // has been saved
    Session["Profile"] = profile;
    return Json(new { success = true });            
}

Next we need to create the two partial views that correspond to the first two action methods we created above. In this example, the partial view for the EditProfile action is pretty much just the stock view created by the MVC frameowork except I have removed the input element to submit the form as we will use the buttons on the jQuery UI dialog to submit it.

The second partial view, the one that displays the saved Profile object again in this example is the stock view with a few added elements.

@using DialogFormExample.MvcHelpers

@model DialogFormExample.Models.Profile

<fieldset>
    <legend>Contact Info</legend>
    
    <div class="display-field">
        Name: @Html.DisplayFor(model => model.Name)
    </div>

    <div class="display-field">
        Nick name: @Html.DisplayFor(model => model.NickName)
    </div>
    
    <div class="display-field">
        Email: @Html.DisplayFor(model => model.Email)
    </div>
    
    <div class="display-field">
        Age: @Html.DisplayFor(model => model.Age)
    </div>

    <div class="right">
        @Html.DialogFormLink("Edit", Url.Action("EditProfile"), "Edit Profile", "ProfileContainer", Url.Action("Profile"))
    </div>
</fieldset>

The first portion of the partial view just displays the form elements required for editing the model. This is just the stock Edit view modified only slightly. The new code starts at line 25. Here I created an extension method for HtmlHelper named DialogFormLink that will create an anchor tag loaded with all the needed information to make the dialog form work. Here is the code for the extension method. You can read the comments to get an understanding of the parameters it requires.

/// <summary>
/// Creates a link that will open a jQuery UI dialog form.
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="linkText">The inner text of the anchor element</param>
/// <param name="dialogContentUrl">The url that will return the content to be loaded into the dialog window</param>
/// <param name="dialogTitle">The title to be displayed in the dialog window</param>
/// <param name="updateTargetId">The id of the div that should be updated after the form submission</param>
/// <param name="updateUrl">The url that will return the content to be loaded into the traget div</param>
/// <returns></returns>
public static MvcHtmlString DialogFormLink(this HtmlHelper htmlHelper, string linkText, string dialogContentUrl,
    string dialogId, string dialogTitle, string updateTargetId, string updateUrl)
{
    TagBuilder builder = new TagBuilder("a");
    builder.SetInnerText(linkText);
    builder.Attributes.Add("href", dialogContentUrl);            
    builder.Attributes.Add("data-dialog-title", dialogTitle);
    builder.Attributes.Add("data-update-target-id", updateTargetId);
    builder.Attributes.Add("data-update-url", updateUrl);

    // Add a css class named dialogLink that will be
    // used to identify the anchor tag and to wire up
    // the jQuery functions
    builder.AddCssClass("dialogLink");

    return new MvcHtmlString(builder.ToString());
}   

The above extension method that builds our anchor tag utilizes the HTML5 data attributes to store information such as the title of the dialog window, the url that will return the content of the dialog window, the id of the div to update after the form is submitted, and the url that will update the target div.

Now we need to add the container div to hold the Profile information in the Index view.

@{
    ViewBag.Title = "Home Page";
}

<h2>@ViewBag.Message</h2>

<div id="ProfileContainer">
    @{ Html.RenderAction("Profile"); }
</div>

Lastly, I have listed the scripts and css files that need to be linked below in the _Layout page for everything to work correctly.

<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("~/Content/themes/base/jquery.ui.all.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery-ui-1.8.11.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>   
    <script src="@Url.Content("~/Scripts/DialogForm.js")" type="text/javascript"></script>    
</head>

The last script reference above is to a script I wrote called DialogForm.js. This script (shown below) will make everything work.


$(function () {

    // Don't allow browser caching of forms
    $.ajaxSetup({ cache: false });

    // Wire up the click event of any current or future 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'); }
                }
            });

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

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

function wireUpForm(dialog, updateTargetId, updateUrl) {
    $('form', dialog).submit(function () {

        // Do not submit if the form
        // does not pass client side validation
        if (!$(this).valid())
            return false;

        // Client side validation passed, submit the form
        // using the jQuery.ajax form
        $.ajax({
            url: this.action,
            type: this.method,
            data: $(this).serialize(),
            success: function (result) {
                // Check whether the post was successful
                if (result.success) {                    
                    // Close the dialog 
                    $(dialog).dialog('close');

                    // Reload the updated data in the target div
                    $(updateTargetId).load(updateUrl);
                } else {
                    // Reload the dialog to show model errors                    
                    $(dialog).html(result);

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

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

Conclusion

I know that was a lot to take in but after you do the setup once, all you need to do is make one call to Html.DialogFormLink and everything will be taken care of for you. Hope this helps!

Follow

Get every new post delivered to your Inbox.

Join 67 other followers