1

I'm currently using a number of methods of generating small and large chunks of HTML, as detailed in the answer here: Helper methods to generate small HTML snippets.

I can also add one extra to that - I'm also using the excellent "using pattern" to create my own HTML container classes, i.e.

using Html.BeginWidget("title")
{
    // Add things inside widget.
}

So far so good, this has proved to be very handy for making the views easy to read and maintain. This is important as part of the point of this job is to allow designers and other technical non-devs to create and maintain their own views, either by making this simple enough for them to work with, or by making a server side tool to generate the "default" form views.

To get to that point, I'm currently trying to standardize the rendering of lists, and encapsulate all of the code behind all of the sort, filter and search. There can be a number of these lists on each view, so it quickly gets out of hand.

My idea was to use the "using pattern" to create the list container, then the "Enumerator pattern" to provide a reference to each item back to the View, as such:

    using (HtmlList<Location> list = Html.BeginList(locations))
    {
        while (list.MoveNext())
        {
            @(list.Current.Item.Name)
        }
    }

There are generic constraints in play on that helper function, so it only accepts models which implement the interfaces it needs to work.

This works quite well, but there is a massive problem - I need to be able to control certain properties on the link itself, while automatically generating others. The Enumerator pattern alone means the link has already been created by the time I get my item reference.

To solve this I'm employing the enumerator and using pattern in tandem inside the list:

    using (HtmlList<Location> list = Html.BeginList(locations))
    {
        while (list.MoveNext())
        {
            list.Current.Url = Url.Action("A", "C", new { id = list.Current.Item.ID });

            using (list.RenderCurrent())
            {
                <img src="/Images/Icons/25/@(list.Current.Item.Icon).png" />
                @(list.Current.Item.Name) <i>(@(list.Current.Item.Type))</i>
                <p>@(list.Current.Item.ShortDescription)</p>
            }
        }
    }

I'm wrapping the item in a HtmlListItem class which gives me the properties I need here. This gives me access to the item prior to it being rendered.

However, this is starting to get a little too complicated for comfort for some of the designers.

Can anyone think of a different approach or any suggestions to simplify this usage pattern?


Cut down version of the classes used in the last example:

HtmlList.cs

public class HtmlList<T> : IDisposable
{
    private HtmlListItem current;
    private IEnumerator<T> enumerator;
    private HtmlHelper html;
    private IEnumerable<T> items;
    private StringBuilder stringBuilder;

    protected internal HtmlList(HtmlHelper html)
    {
        this.html = html;
    }

    public HtmlListItem Current { get { return this.current; } }

    public IEnumerable<T> Items
    {
        get { return this.items; }
        protected internal set
        {
            this.items = value;
            this.enumerator = this.items.GetEnumerator();
        }
    }

    public void Dispose()
    {
        this.EndRender();
    }

    public bool MoveNext()
    {
        if (this.enumerator.MoveNext())
        {
            this.current = new HtmlListItem(html, this.enumerator.Current);
            return true;
        }

        this.current = HtmlListItem.None;
        return false;
    }

    public HtmlListItem RenderCurrent()
    {
        return this.current.BeginRender();
    }

    protected internal virtual HtmlList<T> BeginRender()
    {
        this.stringBuilder = new StringBuilder();

        this.stringBuilder.Append("<div class=\"list\">");

        this.html.ViewContext.Writer.Write(stringBuilder.ToString());
        this.stringBuilder = null;

        return this;
    }

    protected internal virtual void EndRender()
    {
        this.html.ViewContext.Writer.Write("</div>");
    }

    public class HtmlListItem : IDisposable
    {
        private static HtmlListItem none = null;
        private string cssClass = "";
        private HtmlHelper html;
        private T item;
        private StringBuilder stringBuilder;
        private string url = "";

        public HtmlListItem(HtmlHelper html, T item)
        {
            this.html = html;
            this.item = item;
        }

        public static HtmlListItem None { get { return HtmlListItem.none; } }

        public string CssClass { get { return this.cssClass; } set { this.cssClass = value; } }

        public T Item { get { return this.item; } }

        public string Url { get { return this.url; } set { this.url = value; } }

        public void Dispose()
        {
            this.EndRender();
        }

        protected internal virtual HtmlListItem BeginRender()
        {
            this.stringBuilder = new StringBuilder();

            this.stringBuilder.Append("<a");
            if (this.Url != "")
            {
                this.stringBuilder.Append(" href=\"" + this.Url + "\"");
            }
            if (this.CssClass != "")
            {
                this.stringBuilder.Append(" class=\"" + this.CssClass + "\"");
            }
            this.stringBuilder.Append(">");

            this.html.ViewContext.Writer.Write(stringBuilder.ToString());
            this.stringBuilder = null;

            return this;
        }

        protected internal virtual void EndRender()
        {
            this.html.ViewContext.Writer.Write("</a>");
        }
    }
}

HtmlListHelpers.cs

public static class HtmlListHelpers
{
    public static HtmlList<T> BeginList<T>(this HtmlHelper html, IEnumerable<T> listItems)
    {
        return new HtmlList<T>(html) { Items = listItems }.BeginRender();
    }
}
Community
  • 1
  • 1
Octopoid
  • 3,507
  • 5
  • 22
  • 43

0 Answers0