Visar inlägg med etikett Extension Methods. Visa alla inlägg
Visar inlägg med etikett Extension Methods. Visa alla inlägg

Meet my new HtmlHelper extensions friends, ForEach and If

Posted by Kenny Eliasson | Posted in , , , | Posted on 13:15

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.