Nested Tag Builder: HTML Tags Easier?

I recently had an ASP.Net MVC project where I wanted to generate some HTML tags in code for an HtmlHelper. Basically making some semi simple reusable code.

Here is what I came up with…

HTML to Generate

First things first, here is the HTML that I wanted to generate, more or less. This is something that will be reused on various dashboards, etc. But the process applies to any code chunk you might re-use.

<div class="row feature-container">
	<a href="#">
		<div class="feature-item col-xs-6 col-sm-6">
			<div class="feature-body">
				<div class="feature-value">1,220</div>
				<div class="feature-name">Total</div>
			</div>
		</div>
	</a>
	<a href="#">
		<div class="feature-last feature-item col-xs-6 col-sm-6">
			<div class="feature-body">
				<div class="feature-value">125</div>
				<div class="feature-name">Count</div>
			</div>
		</div>
	</a>
</div>

With the styling I have, here is what that code ends up looking like, more or less. It’s a work in progress for some PoC designs.

htmlhelper-display

 

The Tag Builder

Building off of the TagBuilder class that is part of Asp.Net MVC, I created a NestedTagBuilder class that adds a bunch of functionality, including the ability to chain commands in a psudo factory design pattern that you will see later in the code.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Web.Mvc;

namespace Beta_Portal.Helpers
{
	public class NestedTagBuilder : TagBuilder
	{
		private readonly IList<NestedTagBuilder> _innerTags = new List<NestedTagBuilder>();

		public NestedTagBuilder(string tagName)
			: base(tagName)
		{

		}

		public IEnumerable<NestedTagBuilder> InnerTags
		{
			get
			{
				return new ReadOnlyCollection<NestedTagBuilder>(this._innerTags);
			}
		}

		public void Add(NestedTagBuilder tag)
		{
			if (tag == null) {
				throw new ArgumentNullException("tag");
			}

			this._innerTags.Add(tag);
		}

		public override string ToString()
		{
			if (this.InnerTags.Count() > 0) {
				this.InnerHtml = RenderSubTags(this);
			}
			return base.ToString();
		}

		private string RenderSubTags(NestedTagBuilder tag)
		{
			StringBuilder sb = new StringBuilder();
			foreach (var t in tag.InnerTags) {
				sb.Append(t.ToString());
			}
			return sb.ToString();
		}

		public static NestedTagBuilder Create(string tagName)
		{
			return new NestedTagBuilder(tagName);
		}

		public NestedTagBuilder AddCss(string css)
		{
			this.AddCssClass(css);
			return this;
		}

		public NestedTagBuilder AddCssIf(bool condition, string css)
		{
			if (condition) {
				this.AddCssClass(css);
			}
			return this;
		}

		public NestedTagBuilder AddChild(NestedTagBuilder tag)
		{
			this.Add(tag);
			return this;
		}

		public NestedTagBuilder SetAttribute(string key, string value)
		{
			this.MergeAttribute(key, value);
			return this;
		}

		public NestedTagBuilder SetText(string text)
		{
			this.SetInnerText(text);
			return this;
		}

		public NestedTagBuilder SetInnerHtml(string html)
		{
			this.InnerHtml = html;
			return this;
		}

	}
}

What this code lets us do is simplify the process of outputting the initial html code. Once you have the NestedTagBuilder code, you can then create an HtmlHelper so you can access the code easily in your Razor views using something like @Html.FeatureItem(…).

 

The HtmlHelper

Here is the HtmlHelper I created for use in Razor views. It has some defaults and is very specific to my application, but you can modify and use it however you would like.

public static HtmlString FeatureItem(this HtmlHelper helper, string name, string value, string url = "", bool borderRight = true, bool borderBottom = true, string css = "col-xs-6 col-sm-6")
{
	NestedTagBuilder tBase = NestedTagBuilder.Create("div")
		.AddCss(css)
		.AddCss("feature-item")
		.AddCssIf(!borderRight, "feature-last")
		.AddCssIf(!borderBottom, "feature-bottom")
		.AddChild(
			NestedTagBuilder.Create("div")
				.AddCss("feature-body")
				.AddChild(
					NestedTagBuilder.Create("div")
						.AddCss("feature-value")
						.SetText(value)
				)
				.AddChild(NestedTagBuilder.Create("div")
					.AddCss("feature-name")
					.SetText(name)
				)
		);

	if (!string.IsNullOrEmpty(url)) {
		tBase = NestedTagBuilder.Create("a")
			.SetAttribute("href", url)
			.AddChild(tBase);
	}

	return new HtmlString(tBase.ToString());
}

If you were to use the stock TagBuilder, the code, to me, is a little messy and harder to nest. Here is what that ends up looking like.

public static HtmlString FeatureItem(this HtmlHelper helper, string name, string value, string url = "", bool borderRight = true, bool borderBottom = true, string css = "col-xs-6 col-sm-6")
{
	TagBuilder tValue = new TagBuilder("div");
	tValue.AddCssClass("feature-value");
	tValue.SetInnerText(value);

	TagBuilder tName = new TagBuilder("div");
	tName.AddCssClass("feature-name");
	tName.SetInnerText(name);

	TagBuilder tBody = new TagBuilder("div");
	tBody.AddCssClass("feature-body");
	tBody.InnerHtml = tValue.ToString() + tName.ToString();

	TagBuilder tDiv = new TagBuilder("div");
	tDiv.AddCssClass(css);
	tDiv.AddCssClass("feature-item");
	if (!borderRight) tDiv.AddCssClass("feature-last");
	if (!borderBottom) tDiv.AddCssClass("feature-bottom");
	tDiv.InnerHtml = tBody.ToString();

	if (!string.IsNullOrEmpty(url)) {
		TagBuilder tLink = new TagBuilder("a");
		tLink.MergeAttribute("href", url);
		tLink.InnerHtml = tDiv.ToString();

		return new HtmlString(tLink.ToString());
	}

	NestedTagBuilder tBase = new NestedTagBuilder("div");
	tBase.AddCssClass(css);
	tBase.AddCssClass("feature-item");
	if (!borderRight) tBase.AddCssClass("feature-last");
	if (!borderBottom) tBase.AddCssClass("feature-bottom");
	
	return new HtmlString(tBase.ToString());
}

Both of these sets of code do the same thing, it’s your call what works better, but I like the true nesting in one form or another.

So, once the helper is created, you can use it in your Razor view using the following code.

<div class="row feature-container">
	@Html.FeatureItem(
		"Total",
		"1,220",
		"#",
		true, false, "col-xs-6 col-sm-6")

	@Html.FeatureItem(
		"Items",
		"15",
		"#",
		false, false, "col-xs-6 col-sm-6")
</div>

That’s it… Then every where you want to render that HTML you can use the helper. The nice thing about this method is for wide spread reuse, if you need to make changes, all you have to do is make those changes in your HtmlHelper.

It makes code management a little easier, if you have a bunch of areas to update anyways. I know it’s saved me a bit of time already.

 

Do you create any Helper classes to save time when coding your applications?

2 comments

  1. what’s the difference in using tag builder and string builder to create a table in a htmlhelper class, or using the HtmlTable?

    1. The TagBuilder is part of the MVC HTML element building helpers, where HtmlTable is part of the System.Web.UI server controls. So they are different animals. If you are using MVC, the HtmlTable object wont be available.

Leave a Reply

Your email address will not be published. Required fields are marked *