Sunday, April 17, 2011

Creating a Validation Label Helper – Part 1

Presenting validation messages is a pain.  Programmers usually tack validation messages on to a User Interface is a way that gets the job done, but also is clunky and detracts from the overall UI design.  Let me show you an example.  Below is a really simple form from my HireFlo application. 

image

Here’s the markup that creates the form fields and buttons.  I just use a simple layout with a label followed by a validation message, and a Textbox created using MVC’s strongly typed Html helper methods.

    <div class="form-row">
        <label class="form-label" for="folder.FolderName" title="The name for this folder" >
Enter a name for your folder:
</label>
        <%= Html.ValidationMessageFor(model => model.Folder.FolderName)%><br /> 
       <%= Html.TextBoxFor(model=>model.Folder.FolderName, new {style="width:300px;", maxlength="50"})%>
</div>
                            
    <div>
        <input id="SaveButton" type="submit" class="button-green" value=" Save Folder " /> 
        <input id="CancelButton" type="button" class="button-gray" value=" Cancel " />          
    </div>

The only validation on the form is that you have to enter a name for the folder you’re creating.  If you don’t, you get a validation error that looks like this.

image

While functional, this doesn’t look very good. The validation message for the field is just kind of tacked on after the label, but where else am I going to put it. I could put it underneath the field, beside the field, but these options all result in extra text, and an interface that looks cluttered to me. 

I’ve become more and more of a minimalist when it comes to UI design.  I want to put as few things on the page as possible.  So I started thinking, what is the function of the label on a form?  It’s there to tell the user what data to put into a given field.  Now what is the purpose of the label on a form with a validation message?  The validation message and the label are both trying to tell you what goes into a given field.  Maybe it makes sense to combine them.  So I tried that out and the results look like this.

image

All right.  This is looking better to me.  The interface is clean and clear.  I have to make sure that my validation messages always start with the field name, and they need to be fairly short, but those both sound like good design constraints so I’m willing to work with them. The ValidationLabel is now the standard way that I’m going to handle error messages in my application. 

So how should I implement the ValidationLabel?  I’m already using the ValidationMessage Html helper method and that works pretty well, I’ll just customize that and add the Label functionality to it.  I write an extension method for HtmlHelper that will take all of the parameters required to behave as both a label and an error message.  If there’s no error, my helper will render a label.  If there is an error, my helper will render an error message.  Simple.  Here’s how my markup will look with the new helper.

    <div class="form-row">
        <%= Html.ValidationLabel("Folder.FolderName", "Enter a name for your folder", null)%>
        <%= Html.TextBoxFor(model=>model.Folder.FolderName, new {style="width:300px;", maxlength="50"})%>
</div>
                           
    <div>
        <input id="SaveButton" type="submit" class="button-green" value=" Save Folder " /> 
        <input id="CancelButton" type="button" class="button-gray" value=" Cancel " />          
    </div>

Hmmmm, cleaner interface and cleaner markup.  I like the way this is going.  Now let’s write the ValidationLabel helper method.  It’s actually surprisingly simple.  Here’s the code.

        // ValidationLabel
        public static MvcHtmlString ValidationLabel(this HtmlHelper htmlHelper,
string modelName,
string labelText,
IDictionary<string, object> htmlAttributes)
        {
            if (modelName == null) { throw new ArgumentNullException("modelName"); }
            ModelState modelState = htmlHelper.ViewData.ModelState[modelName];
            ModelErrorCollection modelErrors = (modelState == null) ? null : modelState.Errors;
            ModelError modelError = ((modelErrors == null) || (modelErrors.Count == 0)) ? null : modelErrors[0];
            // If there is no error, we want to show a label.  If there is an error,
            // we want to show the error message.
            string tagText = labelText;
            string tagClass = "form_field_label_normal";
            if ((modelState != null) && (modelError != null))
            {
                tagText = modelError.ErrorMessage;
                tagClass = "form_field_label_error";
            }
            // Build out the tag
            TagBuilder builder = new TagBuilder("span");
            builder.MergeAttributes(htmlAttributes);
            builder.MergeAttribute("class", tagClass);
            builder.MergeAttribute("validationlabelfor", modelName );
            builder.SetInnerText(tagText);
            return MvcHtmlString.Create(builder.ToString(TagRenderMode.Normal));
        }

Here’s how it works.  This is an extension method on the HtmlHelper class so the first parameter is the HtmlHelper.  Next we pass in the modelName, the labelText, and a dictionary of Html Attributes that can be used for custom formatting.  The first thing we do is make sure that we have a modelName and then check to see if there’s an item in the ModelErrorsCollection for that modelName. 

Note that modelName is not the name of your entire model, it’s the name of your field.  In my folder form example the model name is “Folder.FolderName”, because I have view model class that contains a Folder object and that Folder object has a property called FolderName.  If you’re ever uncertain about what a field’s modelName is, just load up your page and look at the markup for your input tag. It will look something like this, and your model name is the value that ASP.Net MVC puts in the name attribute for your tag.

<input id="Folder_FolderName" maxlength="50" name="Folder.FolderName" style="width:300px;" type="text" value="">

Let’s get back to our method. Now we know we have a modelName, we know what the modelName is, and we’ve checked the ModelState’s ModelErrorsCollection to see if we have any errors for our modelName.  From this point on the logic is pretty simple. If we do have an error we set the tagText to the error message text and we set the tagClass to “form_field_label_error”.  If we don’t have an error we set the tagText to the labelText and we set tagClass to “form_field_label_normal”.  Then we just use a TagBuilder to render out a span using the tagText and tagClass that we defined.  I also added a custom “validationlabelfor” attribute that holds the model name and is used for client side (javascript) validation.

So that it! We now have a method that renders our field label, and will magically present any error messages for that field if they exist in the ModelState (In case you’re wondering, ASP.Net MVC automatically stores all errors resulting from model validation or model binding in the ModelState).  Plus the syntax is really clean and simple.  A ValidationLabel, input pair is just 2 lines.

          <%= Html.ValidationLabel("Folder.FolderName", "Enter a name for your folder", null)%>
        <%= Html.TextBoxFor(model=>model.Folder.FolderName, new {style="width:300px;", maxlength="50"})%>

There is one more thing I want to do though.  Notice how my ValidationLabel requires me to manually enter the modelName “Folder.FolderName”, but the TextBoxFor method uses a lambda instead?  That’s pretty cool, plus it eliminates the need to use a “magic string” in our UI code.  For Part 2 I’m going to cover how we can create our very own strongly-typed ValidationLabelFor method that will take a lambda as a parameter instead of that modelName string.