Exceptions Gone Wild

OK.  So I'm playing around again with PrototypeDotNetNotReally.  I got to looking at this code sample:


new Element("select", "id=select1;")
  .Insert(new Element("option","value=1").Update("Chris"))
  .Insert(new Element("option","value=2").Update("Anja"))
  .Insert(new Element("option","value=3").Update("Riley"))
  .Insert(new Element("option","value=4").Update("Emmitt"))
  .Render(Console.Out);

1
2
3
4
5
6

It's alot of code, not too clean looking.  So I wanted to come up with a simpler and shorter way of doing the same thing. 

Write The Test First

So I started writing the code I wanted to write to achieve the desired outcome. Here's what I got:


[Test]
public void CanCreateSelect()
{
  string expected =
    "" +
    "Chris" +
    "Anja" +
    "Riley" +
    "Emmitt" +
    "";
  string actual = new Select("select1")
    .AddOption(1, "Chris")
    .AddOption(2, "Anja")
    .AddOption(3, "Riley")
    .AddOption(4, "Emmitt")
    .ToString();
  Assert.AreEqual(expected, actual);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Run The Test and See If It Fails

I hit CTRL+SHIFT+T which is wired up to the TestDriven.NET client, and voila! Errors galore.

Write Some Code

Kind of a lot of errors, right? I haven't even written a Select class yet. So, I declare a class named Select.

Run The Test Again

I run the test and now it complains that there's no constructor on Select that accepts a string,and there's no declaration of the methods AddOption and Render.

Write Some More Code

OK, I declare the contructor, declare AddOption, but the Render method I don't want to reinvent, there's already one in the Element class I want to use. So I inherit from Element and I should be good to go.

Run The Test Again

Now it compiles but the test failed.  None of the options were rendered... The default implementation of Element's Render method handles rendering itself and it's own children as well as it's own attributes.

Select inherits from Element.  Element has no knowledge of any of Select's members.  So I need to be able to hook into the Render method and tell it to Render the Select's children(the Options).  

The Render method is already virtual, so I can override that and use the HtmlTextWriter that's passed in to do all the dirty work.   But I don't want to type in a ton of code that is already in Element...

Refactor

Here's the original Render method:


override public void Render(HtmlTextWriter html)
{
  if (this.Attributes.Count == 0)
    html.WriteFullBeginTag(this.TagName);
  else
  {
    html.WriteBeginTag(this.TagName);
    foreach (Attribute attr in this.Attributes)
    {
      html.WriteAttribute(attr.Name, attr.Value);
    }
    html.Write(HtmlTextWriter.TagRightChar);
  }
  foreach (Element child in Children)
  {
    child.Render(html);
  }
  if (!String.IsNullOrEmpty(this.InnerHtml))
    html.Write(this.InnerHtml);
  html.WriteEndTag(this.TagName);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

I need to intercept a few things.  I need to break out when the opening tag is being rendered, when the attributes are being rendered, when the content is being rendered and when the end tag is being rendered. So the Render method ends up having 4 method calls only, using Refactor! Pro makes this a snap with the Extract Method refactoring. Now I have this:


override public void Render(HtmlTextWriter html)
{
  RenderStartTag(html);
  RenderChildren(html);
  RenderContent(html);
  RenderEndTag(html);
}

1
2
3
4
5
6
7

The RenderStartTag actually was refactored to also call RenderAttributes. Here are those methods:


protected virtual void RenderAttributes(HtmlTextWriter html)
{
  foreach (Attribute attr in this.Attributes)
  {
    html.WriteAttribute(attr.Name, attr.Value);
  }
}
protected virtual void RenderStartTag(HtmlTextWriter html)
{
  html.WriteBeginTag(this.TagName);
  RenderAttributes(html);
  html.Write(HtmlTextWriter.TagRightChar);
}
protected virtual void RenderChildren(HtmlTextWriter html)
{
  foreach (Element child in Children)
  {
    child.Render(html);
  }
}
protected virtual void RenderEndTag(HtmlTextWriter html)
{
  html.WriteEndTag(this.TagName);
}
protected virtual void RenderContent(HtmlTextWriter html)
{
  if (!String.IsNullOrEmpty(this.InnerHtml))
    html.Write(this.InnerHtml);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

Nothin special however now my Select class can take advantage of those virtual methods. I only needed to override the RenderChildren method so I could get my options to render, here's the entire Select class in all it's glory:


public class Select : Element
{
  private List _options;
  public List Options
  {
    get
    {
      if (_options == null)
        _options = new List();
      return _options;
    }
  }
  private string _id = "";
  public string Id
  {
    get
    {
      return _id;
    }
  }
  public Select(string id)
    : base("select", "id="+id+";name="+id)
  {
    _id = id;
  }
  public virtual Select AddOption(object value, string text)
  {
    this.Options.Add(new Option(value, text));
    return this;
  }
  protected override void RenderChildren(HtmlTextWriter html)
  {
    foreach (Option option in this.Options)
    {
      option.Render(html);
    }
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

Here's the Option class, super simple, almost don't even need it, that may get refactored out, not sure at this point. I ended up overriding the RenderContent and RenderAttributes methods.


public class Option : Element
{
  private object _value;
  private string _text;
  public string Text
  {
    get
    {
      return _text;
    }
  }
  public Option(object value, string text)
    : base("option")
  {
    _value = value;
    _text = text;
  }
  protected override void RenderAttributes(HtmlTextWriter html)
  {
    base.RenderAttributes(html);
    html.WriteAttribute("value", this._value.ToString());
  }
  protected override void RenderContent(HtmlTextWriter html)
  {
    html.Write(this.Text);
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

Are We Done?

YES.  As soon as the test passes we're done.  I might continue writing tests that validated input, etc, but feature wise, we're done. We've written the least amount of code possible to achieve the desired behavior.

What Did We Gain?

The Select and Option classes are small and easy to read. They contain only the code necessary for the classes to do their job and nothing more.  No fluff. Just for kicks I reran the code coverage, now what's the percentage? We started with 100%, and now what do we have...(drum roll)...100%!  Adding only the necessary code to make the test pass guarantees that we maintain 100% code coverage.

 
Author: , 0000-00-00