Monday 30 May 2011

Create a custom MVC Html Helper Method For ToolTips

HtmlHelper methods in MVC are great, they cut down on so much code, your view markup looks so much cleaner and so much more readable, they interact with your model and in general are very widely used. One of the great things about the HtmlHelper (and MVC in general) class is it's extensibility, you can quite easily create your own extension methods and create lots of extra re-usable functionality throughout your site. There is a small bit of work involved in the set up but I have found it's well worth it.

This post expects you have some experience in creating simple MVC projects.

This particular helper method is designed to allow a developer to easily add tool tips to various elements on their MVC web site, the goal is to be able to do something like this in your view

@Html.TextBoxFor(x => x.Name)
@Html.ToolTipMessageFor(x => x.Name)

When the text box is in focus, the ToolTip should render, when the text box loses focus it will vanish.

How to do it...

So go ahead and create yourself a new MVC3 (I am using Razor as a View Engine (I always do) but you can modify the appropriate markup easily enough) project called HtmlHelperExample, just select the defaults for the project (not a completely empty project as we will use the generated controllers etc).

Create yourself a model in the Models folder, I have created one called Player that will look a little like this...




We are then going to create a new attribute we can decorate these properties with, called ToolTip.

In order to do this, we need to create a new folder (to keep things nice and neat of course) called Attributes, and in there a new class called ToolTipAttribute, this class is going to inherit from System.ComponentModel.DescriptionAttribute, the reason we create a new class is in case we want to override any default behaviour and also distinguish between what a ToolTipAttribute and a DescriptionAttribute mean, you could have both attributes on a property that do two different things.

Last thing we need to do with this, is set up a constructor that in turn call the base constructor, I am also hiding the default constructor to force a message to be set every time a ToolTip attribute is constructed. When finished, the class will look like this.

Build the project and you can now decorate your player class in the usual way but now the ToolTip attribute is available. Add a Required attribute to the Position property, will explain why in a little bit...


Create a typical action result on the Home Controller called AddPlayer, and add a view (it should be strongly typed using our Player class) for this for adding a player to an imaginery database/repository, and start the project to make sure you can browse to the page, click submit and you should get an validation error moaning about the Position field being required, and also the NumberOfPremiershipsWon will complain as it is an integer and the javascript doesn't infer empty text boxes as zeroes.




Now I have a working project that expects a model that contains properites decorated with our ToolTip method, we are now ready to start building our HtmlHelper Method class.

Create a new folder in the project called HtmlHelperExtensions, and in here create a class called ToolTipHelper. This class needs to be static as we are going to be creating a an extension method in here shortly.

Have a think about the 3 @Html.ValidationMessageFor we have just created (if you used the VS2010 scaffolding wizard), we basically pass in a lambda expression from our model explaining what we want to validate, and it returns a piece of Html based on the attributes of that property, this is exactly the functionality we want, except our method will be scouring the model for a different attribute (Our ToolTipAttribute), what this means is we can use the exact same extension method signature for our own extension method.


The code inside this method is as follows


The first line of note is

var exp = (MemberExpression)expression.Body;

which basically extracts the Model and it's property name from the expression passed in.

Then the loop starts through that property's attributes until it finds either our new ToolTipAttribute or a Required attribute. It's completely up to you but I have written this in such a way that a ToolTip attribute will always override a Required attribute, but to get tool tips immediately up and running right throughout your site, you can use the Required attribute that should already be implemented all over your models. You can then fine tune later with your new ToolTip attribute. Hence why I put a required attribute on 'Position' before. Up to you really.

I have also created a helper method PerformMessageBuild() in this class that will return a piece of Html based on what it is given. This will add a dynamic class to the span element that is generated so we can target the span later with jQuery.


Build your project and that is it. Now you can use this in your view anywhere in your site like this.


VS2010 intellisense might not like your new statement at first but it does work and eventually it sorts itself out.

Running the app now should make no actual difference to the page as the span we have created was set to be hidden by default.

We can sort that with some jQuery so when you click in and out of the text boxes, our tool tip span is displayed, using our dynamic span classes.





And that should be it. You might want to style your tooltip to be standing out and maybe add some fancy fading animation or whatever. You also might want to expand on the jQuery to hide the tool tip message when the validation message is showing for that property, all this can be achieved quite easy by tweaking the jQuery.

You can download this sample here.
Please leave a comment if this was (or wasn't!) any use to anyone, along with any other thoughts.

Thanks
Jason

No comments: