Meet my new HtmlHelper extensions friends, ForEach and If

by Kenny Eliasson 20. February 2010 10:08

How many times have you written code like this in your MVC views?


<%
 int currentIndex = 0;
 foreach(var item in Model.Items) {
%>
    <% if(currentIndex == 0) {%>
       <div class="item first"><%= item.Name %></div>
    <% } else if(currentIndex == Items.Count()-1) { %>
       <div class="item last"><%= currentIndex + 1 %> <%= item.Name %></div>
    <% } else { %>
       <div class="item"><%= item.Name %></div>
    <% } %>
<%
 currentIndex++;
 }
%>

This is pure ugliness :(

What I missed was something like django for variables. They include both a Last and First variable on the loop.

After reading Phil Haacked's blog about "A code based repeater for .NET MVC" I decided to shamelessy take some parts of his code and write my own extension methods.

The first extension method I wrote was the Html.ForEach which gets me a index counter.

public static void ForEach<T>(this HtmlHelper html, IEnumerable<T> items, Action<T, int> render)

{

    if (items == null)

        return;

    int i = 0;

    items.ForEach(item => render(item, i++));

}

And to use it


<% Html.ForEach(Model.Items, (item, index) => { %>
    <div>#<%= index + 1 %> <%= item.Name %>
<% }); %>

Pretty slick and I dont need to have the counter variable in the view code. It uses lambda in way I didn't thought was possible before I read Haacked blog post.

I then decided to see if I could manage to check if I was on the first or last item in the loop.

I came up with this

public static void ForEachExtra<T>(this HtmlHelper html, IEnumerable<T> items, Action<T, ForLoop<T>> render)

{

    if (items == null)

       return;

    var loop = new ForLoop<T>(items);

    int i = 0;

    items.ForEach(item => { render(item, loop.Update(i)); i++; });

}

public class ForLoop<T>

{

    private readonly int _itemCount;
    public ForLoop(IEnumerable<T> items)

    {

        _itemCount = items.Count();

    }

    public int Counter { get; set; }

    public int Counter0 { get; set; }

    public bool First { get; set; }

    public bool Last { get; set; }

    public ForLoop<T> Update(int i)

    {

        Counter = i + 1;

        Counter0 = i;

        First = i == 0;

        Last = i == _itemCount-1;

        return this;

    }

 }

And to use this you will write

<% Html.ForEachExtra(Model.Templates, (template, loop) => {%>

   <% if(loop.First) { %> First! <% }%>">

   <div>#<%= loop.Counter %> - <%= template.Name %></div>

   <% if(loop.Last) { %> Last! <% }%>

<% }); %>

Starting to look impressive here! :) But I still think I can get it better, so onto my other new extension method, If()!

What I want is to have code that looks something like this


<% Html.ForEachExtra(Model.Items, (item, loop) => { %>
   <div class='item <%= Html.If(loop.First).Write("first").ElseIf(loop.Last).Write("last") %>
       #<%=loop.counter%> <%= item.Name %'
>
   </div>
<% }); %>

That would be awesome so off I went to write it.

public interface IConditionWrite

{

    IElseIfConditionBuilder Write(string output);

}

public interface IElseIfConditionBuilder

{

    IConditionWrite ElseIf(bool condition);

}

public class ConditionBuilder : IConditionWrite, IElseIfConditionBuilder

{

    private readonly IList<FluentHtmlCondition> _conditions;

    private FluentHtmlCondition _lastAddedCondition;

    public ConditionBuilder()

    {

        _conditions = new List<FluentHtmlCondition>();

    }

    public IConditionWrite AddCondition(FluentHtmlCondition condition)

    {

       _lastAddedCondition = condition;

       _conditions.Add(condition);

       return this;

    }

    public override string ToString()

    {

        foreach (var condition in _conditions) {

            if (condition.Fulfilled)

                return condition.Output;

        }

        return "";

    }

    public IConditionWrite ElseIf(bool condition)

    {

        AddCondition(new FluentHtmlCondition(condition));

        return this;

    }

    public IElseIfConditionBuilder Write(string output)

    {

        _lastAddedCondition.Output = output;

        return this;

    }

}

 

public class FluentHtmlCondition

{

    public readonly bool Fulfilled;

    public string Output;

 

    public FluentHtmlCondition(bool fulfilled)

    {

        Fulfilled = fulfilled;

    }

    public void Write(string output)

    {

        Output = output;

    }

 }

And in my Html-helper extension class i add

public static IConditionWrite If(this HtmlHelper html, bool condition)

{

    return new ConditionBuilder().AddCondition(new FluentHtmlCondition(condition));

}

And there you have it all, hope someone can have some use with it.

Categories: C#

Comments

trackback
DotNetKicks.com on 2/26/2010 11:46:33 AM

Meet my new HtmlHelper extensions friends, ForEach and If

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading