In the last posting, we saw how the builder pattern could be used to programmatically create forms.  It works fairly well, however we have a new requirement: users need to group several "controls" into "sections".  For the sake of maximum flexibility, we'd like to give users the option to use grouped controls (or "sections") with regular individual controls in the same form. 

The idea is for something like this:

section

The gray box is the "section".  It is comprised of four controls with its sectional header being "Personal Information".  In a way a section is a control made up of other controls and so we'd like for client code to treat it like any other control.

Luckily, there is a pattern that is tailor made for such an scenario.

The Composite Pattern

"Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly."

 

In the case of our requirements, the Section is the Composite and the individual controls are the leaves.  Both need to inherit from a common base class.  If you've read the post about builder then you might recall that each control already inherits from a class called ControlBuilder.

[code:c#]

namespace DynamicForm.Model
{
    //Encapsulates construction of control..delegates details to derived classes.
    public abstract class ControlBuilder
    {

        public string BuildControl()
        {
            this.OpenTag();
            this.SetAttributes();
            this.SetContents();
            this.CloseTag();
            return this.RenderHTML();
        }

        protected abstract void OpenTag();
        protected abstract void SetAttributes();
        protected abstract void SetContents();
        protected abstract void CloseTag();
        protected abstract string RenderHTML();
    }
}

[/code]

We can clearly see that anything that inherits from ControlBuilder is in fact an control.  This is perfect since we define our section as being just another form of an control.  Given that discovery, we can see that ControlBuilder is our Component.  Following the UML for composite, our Section class must inherit from ControlBuilder.

[code:c#]

namespace DynamicForm.Model
{
    public class Section:ControlBuilder
    {
        private IList<ControlBuilder> controls;
        private string html;
        private string sectionName;

        public Section(string sectionName)
        {
            controls = new List<ControlBuilder>();
            this.sectionName = sectionName;
        }

        public void AddControl(ControlBuilder control)
        {
            if (!(control is Section))
            {
                controls.Add(control);
            }
        }

        protected override void OpenTag()
        {
            html += "<div ";
        }

        protected override void SetAttributes()
        {
            html += "class=\"wrapper\">";
        }

        protected override void SetContents()
        {
            html += "<div class=\"right\">";
            foreach (ControlBuilder control in controls)
            {
                html += control.BuildControl() + "<br />";
            }
            html += "</div>";
            html += "<div class=\"left\">" + this.sectionName + "</div>";

        }

        protected override void CloseTag()
        {
            html += "</div>";
        }

        protected override string RenderHTML()
        {
            return this.html;
        }
    }
}

[/code]

You may notice that our Section is an ControlBuilder and also contains a collection of ControlBuilders.  As such, it would be possible for a Section to contain another Section inside of its controls collection.  This is powerful however we do not want this functionality for our application (things could get messy in the U.I. very quickly with a few nested Sections) so we'll just check to make sure the control isn't a Section before adding it to the collection.

Our Client class (DynamicForm) remains ignorant of the idea of a section.  Just as before, it only holds a reference to a collection of ControlBuilders and it only knows how to call the BuildControl method.

[code:c#]

namespace DynamicForm.Model
{
    public class DynamicForm
    {
        private IList<ControlBuilder> controls = new List<ControlBuilder>();

        public void AddControl(ControlBuilder control)
        {
            controls.Add(control);
        }

        public string Display()
        {
            string html = string.Empty;

            foreach (ControlBuilder builder in controls)
            {
                html += builder.BuildControl();
                html += "<br /><br />";
            }

            return html;
        }
    }
}

[/code]

If you put this into a controller:

[code:c#]

  DynamicForm form = new DynamicForm();

            form.AddControl(new ParagraphBlockBuilder(paragraph));
            Section personalInfoSection = new Section("Personal Infomation:");
            personalInfoSection.AddControl(new TextBoxBuilder("First Name"));
            personalInfoSection.AddControl(new TextBoxBuilder("Middle Name"));
            personalInfoSection.AddControl(new TextBoxBuilder("Last Name"));

            List<ControlItem> genderItems = new List<ControlItem>();
            genderItems.Add(new ControlItem("Male", "male"));
            genderItems.Add(new ControlItem("Female", "female"));

            personalInfoSection.AddControl(new CheckBoxBuilder("Gender", genderItems));
            form.AddControl(personalInfoSection);

            form.AddControl(new TextBoxBuilder("Comments"));

return View(form);

[/code]

[code:c#]

<asp:Content ID="aboutContent" ContentPlaceHolderID="MainContent" runat="server">
    <h2>About Us</h2>
    <p>
        <%= ViewData.Model.Display() %>
    </p>
</asp:Content>

[/code]

The view would output something that looked like this:

result

This form contains both a section and a unassociated control.

As I mentioned before, you'd want to keep that sort of hard coding out of the controller.  It would defeat the purpose since the generated form wouldn't be dynamic at all.  What you really want to do is to create some sort of U.I. for creating forms.  Then store which controls make up a form in a database.  There should be a controller that gets the schema from the database and then creates the form at runtime for the user.