Search code examples
twitter-bootstrap-3fubumvc

Using .WrapWith when creating HtmlTag controls


I am creating some HTML Helper methods based on the Fubu project's HtmlTags library for an application based on the inevitable Twitter Bootstrap framework. For most of the simple controls, I have had no problems directly sub-classing HtmlTag; however, when it comes to the more complex controls, which usually need to be nested within a div tag, it gets a little more complex.

For instance, I have a TextboxWidget, which is implemented something like this:

public class TextboxWidget : HtmlTag
{
    public TextboxWidget() : base("input")
    {
        Attr("type", "text");
        AddClass("form-control");
        NoClosingTag();
    }
}

which is pretty straightforward. It can then be used like this (with a few other extension methods):

@Html.Bootstrap().Textbox(m => m.FirstName).Data("bind", "Employee.FirstName")

to give the desired result.

My implementation of a DatepickerWidget goes something like this:

public class DatepickerWidget : HtmlTag
{
    public DatepickerWidget() : base("input")
    {
        Attr("type", "text");
        Attr("role", "datepicker");
        AddClasses("form-control", "datepicker");
        Data("dateformat", "dd/mm/yy");

        Button = new HtmlTag("span").AddClass("input-group-addon");
        var icon = new HtmlTag("i").AddClass("icon-calendar");

        Button.Append(icon);
        Append(Button);
        WrapWith(new DivTag().AddClass("input-group"));
    }
}

which is then used like this:

@Html.Bootstrap().Datepicker(m => m.StartDate).DataBind("click", "fnProcessClick").AddClass("special-offer")

but for some reason, the wrapping div tag is not being rendered. If I write some unit tests for the control, then I can see the div tag in the object model (in the Parent property), but it just never gets turned into HTML on the page.

I really want to keep the main input element as the focal element of the control, so all the nice HtmlTag extensions hang off the widget; will I have to go down the route displayed by Jimmy Bogard by exposing modifiers for each of the individual elements within the control?


Solution

  • WrapWith returns a new HtmlTag; in your implementation of DatePickerWidget it basically just gets ignored, and the DatePickerWidget is still just the "input" tag you based it on.

    One option would be to drop the WrapWith line from your class add the wrapper later:

    @Html.Bootstrap().Datepicker(m => m.StartDate).DataBind("click", "fnProcessClick").AddClass("special-offer").WrapWith(new DivTag().AddClass("input-group"))

    If that's cumbersome, you could shorten it by adding a method to the class for it:

     public HtmlTag WrapWithInputGroup()
     {
        return WrapWith(new DivTag().AddClass("input-group"));
     }
    
    @Html.Bootstrap().Datepicker(m => m.StartDate).DataBind("click", "fnProcessClick").AddClass("special-offer").WrapWithInputGroup()
    

    Another option which works the way you expected your original class to work would be to override ToString():

    public class DatepickerWidget2 : HtmlTag
    {
        public DatepickerWidget2()
            : base("input")
        {
            Attr("type", "text");
            Attr("role", "datepicker");
            AddClasses("form-control", "datepicker");
            Data("dateformat", "dd/mm/yy");
    
            var Button = new HtmlTag("span").AddClass("input-group-addon");
            var icon = new HtmlTag("i").AddClass("icon-calendar");
    
            Button.Append(icon);
            Append(Button);
        }
    
        public override string ToString()
        {
            return WrapWith(new DivTag().AddClass("input-group")).ToString();
        }
    }
    

    Huge, huge caveat: in my quick-and-dirty testing this works, but that might be counter to the original design of HtmlTags. I'd use this solution with caution (until/unless somebody like Jeremy Miller shows up here and says it's the right solution).