Posts tagged with: htmlhelpers

First draft of my alternative HTML helpers

I’m getting closer to a first release of my ASP.NET MVC contribution project called Griffin.MvcContrib. I’ve just committed my first draft of the alternative HTML helpers. I’ve focused on helpers for Form tags in this commit. If you are wondering what’s wrong with the original helpers, please see my previous blog entry about using extension as an important part of a framework.

Here are some new features that my HTML providers add:

Extensible

By using extension methods there is no way to extend the functionality in them. I’ve solved this in two ways. First and foremost there aren’t any extension methods. My HtmlHelper class is just a facade that tries to use the DependencyResolver to find the proper generators. It will also create the default implementations if no extended ones are found.

To extend one of the helpers, simply derive it and make the changes you like. The following sample with add a “title” attribute to all text areas (which then can be picked up by a jquery script to create a nice tool tip)

    public class MyCustomTextAreaGenerator : TextAreaGenerator
    {
        public MyCustomTextAreaGenerator(ViewContext viewContext) : base(viewContext)
        {
        }

        protected override IEnumerable<NestedTagBuilder> GenerateTags()
        {
            var generatedTags = base.GenerateTags().ToArray();
            generatedTags[0].MergeAttribute("title", "You are so dirty!");
            return generatedTags;
        }
    }

and then register it in your current inversion of control container. Here is a sample for autofac:

containerBuilder
    .RegisterType<MyCustomTextAreaGenerator>()
    .AsImplementedInterfaces();

The following generators do currently exist:

  • CheckBoxGenerator
  • RadioButtonGenerator
  • SelectGenerator
  • TextAreaGenerator
  • TextBoxGenerator

Post processing generated HTML tags

You can also add more generic adapters which can be used to transform the generated HTML tags. The sample project which is included in github shows you how to:

  • create watermarks for textboxes
  • Add tooltips
  • Transform all age fields into UI sliders

Here is the watermark example:

    public class WatermarkAdapter : IFormItemAdapter
    {
        public void Process(FormItemAdapterContext context)
        {
            // there is a "Watermark" metadata but it cannot currently be set.
            // unless you are using a custom metadata provider like the one in the localization demo.
            if (string.IsNullOrEmpty(context.Metadata.Description) || !context.TagBuilder.Attributes.ContainsKey("class") || !context.TagBuilder.Attributes["class"].Contains("watermark"))
                return;

            context.TagBuilder.MergeAttribute("title", "Watermark:" + context.Metadata.Description);
        }
    }

And the jquery script creating the actual watermark (uses the jquery watermark plugin):

function watermark() {
    $('[title*="Watermark:"]').each(function () {
        $(this).watermark($(this).attr('title').substr(10));
        $(this).removeAttr('title');
    });
}

Enum galore

Are you using enums? Sure you do. The html helpers in Griffin.MvcContrib can generate lists, checkboxes and radio buttons automatically for them.

The following code:

@Html2.CheckBoxesFor(model => model.InputType) <br />
@Html2.RadioButtonsFor(model => model.InputType) <br />
@Html2.DropdownFor(model => model.InputType) <br />

Generates the following HTML:

HTML generated by enum html helpers

Lets format that dropdown

Dropdowns are something else that I’ve tried to improve. You can use formatters to determine how the select lists should be generated.

The following example are using a custom formatter which has been made to transform user objects:

@Html2.DropdownFor(model => model.CurrentUser, Select.From<UserFormatter>(Model.Users))

And the actual formatter:

    public class UserFormatter : ISelectItemFormatter
    {
        public SelectListItem Generate(object itm)
        {
            var item = (User)itm;
            return new SelectListItem
            {
                Text = string.Format("{0} {1}", item.FirstName, item.LastName),
                Value = item.Id.ToString()
            };
        }
    }

There are to formatters built into MvcContrib:

  • IdTitle
  • IdName

They uses reflection to find the proper properties.

Summary

All mentioned helpers should work without any problems. But I’m not done with the helpers yet. This post is most about me wanting feedback. Have I made any stupid mistakes? Do you have any suggestions how I can improve the code?

Please take a look at the sample project

The actual source code for all helpers can be found here


Why extension methods should not be used as part of a framework.

Two of my favorite frameworks/libraries uses extension methods heavily. Autofac uses extension methods during registration and ASP.NET MVC3 for their HTML Helpers. Since extension methods are static, they are closed for extension. Which are one of the most important principles for object oriented programming.

For MVC I would like to be able to add a tooltip automatically for all editors and labels. It’s really simple, I just want to add a “title” attribute to all generated HTML elements where the ModelMetadata contains a “Description”. The source code for LabelFor looks like this:

    public static class LabelExtensions {
        public static MvcHtmlString Label(this HtmlHelper html, string expression) {
            return LabelHelper(html,
                               ModelMetadata.FromStringExpression(expression, html.ViewData),
                               expression);
        }

        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
        public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression) {
            return LabelHelper(html, 
                               ModelMetadata.FromLambdaExpression(expression, html.ViewData), 
                               ExpressionHelper.GetExpressionText(expression));
        }

        public static MvcHtmlString LabelForModel(this HtmlHelper html) {
            return LabelHelper(html, html.ViewData.ModelMetadata, String.Empty);
        }

        internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName) {
            string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
            if (String.IsNullOrEmpty(labelText)) {
                return MvcHtmlString.Empty;
            }

            TagBuilder tag = new TagBuilder("label");
            tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
            tag.SetInnerText(labelText);
            return tag.ToMvcHtmlString(TagRenderMode.Normal);
        }
    }

Hence it’s impossible to do anything about the helper. I do understand why they use extension methods. It’s great since it’s very easy to find all available helpers. The problem is that you can’t do anything do modify the data that they generate.

The solution

Convert all extension methods to facades for the real implementation, and use DependencyResolver in the extension methods to find the proper classes. Create some interfaces and default implementations for all generators.

The new LabelHelper:

internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName)
{

    var labelGenerator = DependencyResolver.Resolve<ILabelGenerator>();
    return labelGenerator.Create(html, metadata, htmlFieldName);
}

The default implementation (simplified example):

	public class LabelGenerator : ILabelGenerator
	{
	
		public virtual void Create(HtmlHelper html, ModelMetadata metadata, string htmlFieldName)
		{
            string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
            if (String.IsNullOrEmpty(labelText)) {
                return MvcHtmlString.Empty;
            }

            TagBuilder tag = new TagBuilder("label");
            tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
            tag.SetInnerText(labelText);
			ProcessTag(tag);
            return tag.ToMvcHtmlString(TagRenderMode.Normal);
		}
			
		protected virtual void ProcessTag(TagBuilder tag, ModelMetaData metaData)
		{
		}
	}

Which would allow me to create the following implementation:

	public class LabelWithTitlesGenerator : LabelGenerator
	{
		protected override void ProcessTag(TagBuilder tag, ModelMetaData metaData)
		{
			if (metaData.Description != null)
				tag.Attributes.Add("title", metaData.Description);
		}
	}

The only thing left is to inject it in my container and create a small jquery script which creates the actual tooltip using the “title” attribute.

Summary

Extension methods are closed for extension. Once declared you can not do anything to extend the functionality that they provide. It’s the same as if you would develop a framework where you put sealed on all your classes.

Extension methods were created to be able to make life easier by proving convenience methods for existing classes. The intention was not that you should use them as a part of your framework since you close down that part for extension if you do. The extension methods become locking out methods ;O