Shrink your Views with FubuMVC Html Conventions

 

Yesterday I was able to “push” into Github the work I’ve been doing for convention based Html generation in FubuMVC.  What the hell is “convention based Html generation?” you ask?  It’s simple in concept.  Your team has policies, formal or informal, for how you build Html elements for the fields in a ViewModel.  If your familiar with MvcContrib’s Opinionated Input Builders or MVC2’s Templates feature (very well described by Brad Wilson here), we’re doing the same thing conceptually but with much different mechanics.  Right now, I’m focused on the very basic building block methods that appear on FubuMVC view’s (remember that we basically force all views in FubuMVC to be strongly typed with a single ViewModel):

  1. InputFor( expression ) – write an Html element to edit a single property on the ViewModel.  Instead of calling TextboxFor(x => x.Something) or DropdownListFor(x => x.Other) we can just say InputFor(x => x.Some.Property.On.The.ViewModel) and let the conventions figure out what to do from there.  If we need to change the model property to something else, or change our conventions about how elements should be constructed, no big deal.
  2. DisplayFor( expression ) – write an Html element to display the value of a single property value on the ViewModel.  String fields are just displayed.  Entity fields may be displayed as a link to more information on that link.  Date fields are shown in short date format, etc.
  3. LabelFor( expression ) – write an Html “label” element for the field.  Out of the box, FubuMVC will just put in the field name, but that’s not all that useful.  In our Dovetail project we have this tied into our localization subsystem to pluck out a localized header text for the desired property.

 

I’m dubious that the form auto-generation stuff that MVC2 does has much real world value, but it would certainly be possible to build on top of what we already have.  In a follow up post I’ll show more of the DSL-ey approach for creating simple forms that uses InputFor/DisplayFor/LabelFor as building blocks.

 

 

Example

In our system we have a small Entity class named Address (all systems of any complexity have an “Address” class, but they’re all different) with a property for the first line of the address called Address1:

        [Required, MaximumStringLength(250)]

        public string Address1 { get; set; }

Leaving the appropriateness of the approach aside (it’s working very well), we use attributes for our simple validation rules (and also use those to generate “not null” / string length types of rules in our database).  On the client side, we use jQuery Validation to do input validation in the browser.  jQuery Validation is very simple to use (as long as you color within the lines).  All I need to do is add classes and some attributes to the <input> and <select> elements to declaratively apply validation rules. 

In usage, we could build the input Html element for a field by just calling InputFor() like this:

    <dd><%= this.InputFor(m => m.Site.PrimaryAddress.Address1).Id(“site-address1″)%></dd>

In our Dovetail system the above call generates this Html:

    <input id=”site-address1″ class=”required” type=”text”

        name=”SitePrimaryAddressAddress1″ label=”Address 1″ maxlength=”250″ value=”"/>

Did you see what happened there?  When we create an input element for the Address.Address1, the conventions:

  1. Built a textbox element.  If the property was a boolean, the conventions would build a checkbox instead.  Likewise, if the property is a DateTime or DateTime? field the conventions would build a date picker.
  2. Set a “name” attribute” that matches our naming convention so that model binding works smoothly from client to server and in automated testing scenarios.  In our case we just take the “path” to a property from the ViewModel and tear out the “.” characters
  3. Adds the “label” attribute with the localized header name for this field.  This is used in conjunction with the jQuery validation to present a clean, localized validation summary on the screen
  4. Recognizes the presence of the [MaximumStringLength] attribute and sets the “maxlength” attribute
  5. Puts the property value into the “value” attribute.  The example I used happens to come from a “New” screen, so the value is blank
  6. Recognizes the presence of the [Required] attribute and adds the “required” class to the element

By the way, did you see how I overrode the “id” property of that textbox?  I’ll talk more about that at the bottom, but that represents a huge advantage that FubuMVC’s model has over both MvcContrib and MVC2.  You can happily modify or enhance the convention-generated Html input on a case by case basis.

 

Can I make my own Conventions?

One of the overarching principles of FubuMVC is to push the usage of Convention over Configuration as far as it can go while still allowing FubuMVC adopters to create and add their own conventions.  The Html conventions are no different.  When you register / configure conventions for the Html you can register either “Builders” that know how to generate the HtmlTag (I’ll explain HtmlTags later) for a certain class of properties:

    public delegate HtmlTag TagBuilder(ElementRequest request);

 

    public interface IElementBuilder

    {

        TagBuilder CreateInitial(AccessorDef accessorDef);

    }

or “Modifiers” that enrich HtmlTag’s:

    public delegate void TagModifier(ElementRequest request, HtmlTag tag);

 

    public interface IElementModifier

    {

        TagModifier CreateModifier(AccessorDef accessorDef);

    }

These ElementBuilder’s and ElementModifier’s can either be classes that you build yourself and “plug” in, or you can use the Html convention DSL shown below to create common, simple types of policies (think, if this attribute exists, add this class).  A custom ElementBuilder might look like this:

    // This builder is only for creating the display tag

    // for a date time field on one of our “activity log”

    // models

    public class LogDateDisplay : ElementBuilder

    {

        // Does this builder apply to a property?

        protected override bool matches(AccessorDef def)

        {

            return def.ModelType.Closes(typeof (LogViewModel<>)) && def.Accessor.PropertyType.IsDateTime();

        }

 

        // Renders a date in long time format and also sticks the header

        // text into the title attribute

        public override HtmlTag Build(ElementRequest request)

        {

            string title = LocalizationManager.GetHeader(request.Accessor.InnerProperty);

            return new HtmlTag(“span”).Text(request.ToDisplay(“{0:F}”)).Title(title);

        }

    }

 

Alrighty, let’s move on to registering our conventions.  From the top, I create a class to hold my project’s Html conventions like so:

    // HtmlConventionRegistry is the base class that provides the DSL

    // This is an example of “Object Scoping” from Fowler speak like

    // StructureMap’s Registry and Fluent NHibernate’s ClassMap

    public class DovetailHtmlConventions : HtmlConventionRegistry

    {

        public DovetailHtmlConventions()

        {

            validationAttributes();

            numbers();

 

            Profile(“edit”, x => x.Editors.Builder<EditInPlaceBuilder>());

 

            editors();

 

            Labels.Builder<LabelBuilder>();

 

            Displays.Builder<LogDateDisplay>();

            Displays.Builder<DisplayBuilder>();

 

        }

 

        private void editors()

        {

            Editors.Builder<ValueObjectDropdownBuilder>();

            Editors.IfPropertyIs<bool>().BuildBy(request => new CheckboxTag(request.Value<bool>()).Style(“width”, “auto !important”).Attr(“value”, request.ElementId));

 

            Editors.Always.Modify((request, tag) =>

            {

                tag.Attr(“label”, request.Header());

                tag.Attr(“name”, request.ElementId);

            });

 

            // Ugly hack because of the hacky Edit in Place jQuery plugin we use, but

            // I’m gonna kill it some day

            Editors.IfPropertyTypeIs(t => t.IsDateTime()).Modify(x =>

            {

                if (!x.HasMetaData(EditInPlaceBuilder.EDITABLE_ATTRIBUTE_NAME))

                {

                    x.AddClass(“DatePicker”);

                }

            });

        }

 

        // Setting up rules for tagging elements with jQuery validation

        // metadata

        // I think that a lot of this gets added into the core Fubu as a

        // “jQueryValidationPack”

        private void numbers()

        {

            Editors.IfPropertyIs<Int32>().Attr(“max”, Int32.MaxValue);

            Editors.IfPropertyIs<Int16>().Attr(“max”, Int16.MaxValue);

            Editors.IfPropertyIs<Int64>().Attr(“max”, Int64.MaxValue);

            Editors.IfPropertyTypeIs(t => t.IsIntegerBased()).AddClass(“integer”);

            Editors.IfPropertyTypeIs(t => t.IsFloatingPoint()).AddClass(“number”);

        }

 

        // Declare policies for using validation attributes

        private void validationAttributes()

        {

            Editors.AddClassForAttribute<RequiredAttribute>(“required”);

            Editors.ModifyForAttribute<MaximumStringLengthAttribute>((tag, att) =>

            {

                if (att.Length < Entity.UnboundedStringLength)

                {

                    tag.Attr(“maxlength”, att.Length);

                }

            });

 

 

            Editors.ModifyForAttribute<GreaterOrEqualToZeroAttribute>(tag => tag.Attr(“min”, 0));

            Editors.ModifyForAttribute<GreaterThanZeroAttribute>(tag => tag.Attr(“min”, 1));

        }

    }

 

HtmlTags

The basis for FubuMVC’s Html conventions, and the big differentiating feature from the MVC2 and MvcContrib approach, is the usage of the “HtmlTags” library as a semantic model.  All the Html generation in FubuMVC works by building up HtmlTag objects that represent an Html element graph (HtmlTag’s can have children).  Think of the old TagBuilder class on steroids.  A picture’s worth a thousand words, so let’s see some samples of HtmlTag in action:

            // Setting the inner text and adding a class

            HtmlTag tag = new HtmlTag(“div”).Text(“my text”).AddClass(“collapsible”);

 

 

            // adding “MetaData” for the jQuery MetaData plugin

            HtmlTag tag = new HtmlTag(“div”).Text(“text”);

            tag.MetaData(“a”, 1);

            tag.MetaData(“b”, “b-value”);

 

        [Test]

        public void render_multiple_levels_of_nesting_2()

        {

            HtmlTag tag = new HtmlTag(“html”).Modify(x =>

            {

                x.Add(“head”, head =>

                {

                    head.Add(“title”).Text(“The title”);

                    head.Add(“style”).Text(“the style”);

                });

 

                x.Add(“body/div”).Text(“inner text of div”);

            });

 

            tag.ToCompacted().ShouldEqual(

                “<html><head><title>The title</title><style>the style</style></head><body><div>inner text of div</div></body></html>”);

        }

The HtmlTag model has been hugely advantageous because:

  1. It greatly simplifies Html construction.  No ugly string bashing, class and attribute merging is handled internally by HtmlTag itself, and we’ve got plenty of convenience shortcuts.  Compare this model to using user controls with tag soup or brute force string manipulation
  2. Allows us to create an incremental model of “policies” to add classes and attributes to elements rather than having to make one big blob of code to create the Html (something that plagues both the MvcContrib and MVC2 designs)
  3. The InputFor(), DisplayFor(), and LabelFor() methods all return the HtmlTag object created by the convention, and HtmlTag itself has fluent builders so that you can override or extend the Html created by the conventions on a case by case basis.  We’ve gotten some great feedback and help with this feature from the MvcContrib guys, and this is their main complaint with using User Controls.
  4. The conventional Html generation is pretty testable in basic xUnit tests without ever having to mess with WatiN or Selenium.  That’s a huge plus in my book

 

HtmlTags is a separate library within FubuMVC that can be used independently (I use it in StoryTeller now).

 

Select Box Example

I’ve had this feature, and even the technical direction, in mind since last summer.  I finally got to build it out for Dovetail usage because we needed to change the way we do configurable lists in our system.  We keep list data in a database table where it’s easily editable and extensible (even to the point of adding cascading relationships after the fact for customizations).  We need to validate certain fields against that list data, so we mark those fields like this:

        // There is no logic associated with Origin

        // other than externalized rules, so having it

        // be a string does no harm

        [Required, ValueOf(ListNames.Origin)]

        public string Origin { get; set; }

Anytime a property is decorated with the [ValueOf] attribute we know that we need to build a dropdown list to edit that field.  All the information that we absolutely need to know to construct that dropdown is available on the property itself, so it made perfect sense to move to convention based Html construction instead of all the repetitive “put list data on ViewModel in Controller, transmit to View, fill options in View code” work we were doing before.  We cache the list data in a static class called ValueObjectRegistry that can give us list data.  Using ValueObjectRegistry and the Html Conventions gave us this:

    // Builds a <select> tag and fills in the list values

    // for any property in our system that is “marked” as being

    // a list field

    public class ValueObjectDropdownBuilder : ElementBuilder

    {

        protected override bool matches(AccessorDef def)

        {

            return def.Accessor.HasAttribute<ValueOfAttribute>();

        }

 

        public override HtmlTag Build(ElementRequest request)

        {

            string defaultValue = request.Value<string>();

 

            if (defaultValue.IsEmpty())

            {

                request.ForListName(name =>

                {

                    ValueObject @default = ValueObjectRegistry.FindDefault(name);

                    if (@default != null) defaultValue = @default.Key;

                });

            }

 

            return new SelectTag(tag =>

            {

                request.EachValueObject(vo => tag.Option(vo.LocalizedText(), vo.Key));

                tag.SelectByValue(defaultValue);

            });

        }

 

        public static HtmlTag Build(string listName)

        {

            return new SelectTag(tag =>

            {

                ValueObjectRegistry.GetAllActive(listName).Each(x =>

                {

                    tag.Option(x.LocalizedText(), x.Key);

                });

 

                var defaultVO = ValueObjectRegistry.FindDefault(listName);

                if (defaultVO != null)

                {

                    tag.SelectByValue(defaultVO.Key);

                }

            });

        }

    }

 

Now that we have ValueObjectDropdownBuilder hooked into our Html conventions, all I need to do on the view is just say:  “InputFor(x => x.Case.Origin)” and let the conventions handle everything.

 

 

Any questions?

About Jeremy Miller

Jeremy is the Chief Software Architect at Dovetail Software, the coolest ISV in Austin. Jeremy began his IT career writing "Shadow IT" applications to automate his engineering documentation, then wandered into software development because it looked like more fun. Jeremy is the author of the open source StructureMap tool for Dependency Injection with .Net, StoryTeller for supercharged acceptance testing in .Net, and one of the principal developers behind FubuMVC. Jeremy's thoughts on all things software can be found at The Shade Tree Developer at http://codebetter.com/jeremymiller.
This entry was posted in FubuMVC. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Arnis L.

    Damn i was stupid. Made fun when FubuMvc just showed up. Now my hands itches wanting to work with it.

  • http://codebetter.com/members/jmiller/default.aspx Jeremy D. Miller

    @Corey,

    Thanks for your comment. I can’t imagine FubuMVC ever being used frequently by the conservative shops, but I see that as our advantage. We need to be able to evolve FubuMVC for awhile and that’s easier with a smaller userbase in many ways.

  • http://blog.coreycoogan.com corey coogan

    I’m really excited about what I see with FUBUMvc. It’s great to see all this effort going into the doc since the reboot.

    Unfortunately, many clients and larger corporations will likely not support any type of mission critical apps to be developed on something so young. I’m sure the same was true for NHibernate when it first started out, but I look forward to this growing into the same quality and community accepted framework so it can actually be a considered an option with more conservative companies.

  • http://codebetter.com/members/jmiller/default.aspx Jeremy D. Miller

    @Kyle,

    1.) This is orthogonal to MVVM — and that’s a pattern for stateful clients anyway

    2.) You can nest view models and follow the dots. There are some details yet to iron out about nested user controls and I haven’t touched enumerable types at all

    3.) Hell yes you wanna test the conventions. If it’s even remotely complicated, yes. There’s a test harness class in FubuMVC to make it a bit easier to do that

    Thanks for your comment,

    Jeremy

  • Kyle Szklenski

    Hey, this is WolfgangSenff on Twitter. It’s kind of curious, the way you’ve done it. It feels a lot like AOP: You’ve pulled out the, “How to build the HTML” aspect and packaged it up. I wonder if such a thing could be done globally, and turn it into, “How to build the GUI”.

    I like the style because it:
    1) Replaces much of the cumbersomeness of building webpages
    2) Has an easy-to-understand naming convention (huge, in my book)
    3) Allows different extensibility points, such as defining your own element builders.
    4) The HtmlTag approach is obviously superior to many other ways of doing it

    My questions to you:
    1) Is this tied to the MVVM presentation pattern?
    2) What if a view-model needs to be composable with other view-models? Does this affect it at all, with the “strip-the-periods” ID piece? I’m not sure that it does at all – just asking.
    3) This might be dumb/incoherent, but do you test the conventions?

    Overall, cool stuff. Thanks for the post.