Advertisement

#24 Picture Tag Helper

In this article we will round out our image related tag helpers by creating a picture tag helper. The code required to generate the required html is a little bit more involved due to the nature of the attributes required for the source tags but we will manage to sort them out here. We finish up by updating our base class so that we can specify a class, id, and/or additional attributes, such as data dash attributes, in the rendered html using our tag helpers.

Advertisement

Not if it is a picture tag

  • WebUi
    • Features
      • Images
        • ImageTagHelper.cs

In the previous article we created a base class that both of our previous image tag helpers inherit from and we will use it again in this case but we need to make a small adjustment to it (a).

ImageTagHelper.cs

...
namespace WebUi.Features.Images
{
    ...
    public abstract class ImageTagHelper : TagHelper
    {
        ...
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (context.TagName != "picture")
            {
                output.TagName = "img";
                output.TagMode = TagMode.StartTagOnly;

                if (IsLazyLoaded)
                {
                    output.AddClass("lazyload", HtmlEncoder.Default);
                }

                output.Attributes.Add("alt", Alt);
            }
            ...
        }
        ...
    }
}
(a) Adjustments made so that we can use the base class with both img tag helpers as well as for our picture tag helper.

The picture tag helper

  • WebUi
    • Features
      • Images
        • PictureTagHelper.cs

Now we are ready to create the picture tag helper (b). This one is of course a bit more involved than the previous two since we have to add a few child elements to the parent picture element.

PictureTagHelper.cs

using System.Linq;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace WebUi.Features.Images
{
    [HtmlTargetElement("picture", Attributes = "break-points")]
    public class PictureTagHelper : ImageTagHelper
    {
        [HtmlAttributeName("break-points")] public string BreakPoints { get; set; }

        protected override void Process(TagHelperOutput output)
        {
            output.TagName = "picture";
            output.TagMode = TagMode.StartTagAndEndTag;

            var breakPoints = BreakPoints.Split(",").Select(int.Parse).ToArray();
            for (var i = 0; i < breakPoints.Length + 1; i++)
            {
                var source = new TagBuilder("source");
                if (i == 0)
                {
                    var max = breakPoints[i] - 1;
                    source.Attributes.Add("media", $"(max-width: {max}px)");
                }
                else if (i == breakPoints.Length)
                {
                    var min = breakPoints[i - 1];
                    source.Attributes.Add("media", $"(min-width: {min}px)");
                }
                else
                {
                    var min = breakPoints[i - 1];
                    var max = breakPoints[i] - 1;
                    source.Attributes.Add("media",
                        $"(min-width: {min}px) and (max-width: {max}px)");
                }

                source.Attributes.Add(IsLazyLoaded ? "data-srcset" : "srcset", ImageSrc(SizeStrings[i]));

                output.Content.AppendHtml(source);
            }

            var img = new TagBuilder("img");

            string srcKey;
            if (IsLazyLoaded )
            {
                srcKey = "data-src";
                img.AddCssClass("lazyload");
            }
            else
            {
                srcKey = "src";
            }
            output.Attributes.Add(srcKey, ImageSrc(SizeStrings[0]));

            output.Content.AppendHtml(img);
        }
    }
}
(b) The code needed to generate the picture tag and all of its children.

Testing things out

  • WebUi
    • Pages
      • Image-Tag-Helper.cshtml

Next up is making sure that our tag helper creates the code we are looking for it to create. To do this we of course return to our razor page and add the code shown in (c).

Image-Tag-Helper.cshtml

...
else if (Model.Problem == "art-direction")
{
    ...
    if (Model.Query == "picture")
    {
        <div class="image-container">
            <picture src-fragment="/images/responsive-images/woman-balloon-chair-dark"
                     sizes="480,640,800"
                     break-points="480,800"
                     extension="jpg"
                     alt="Woman sitting on a chair beside a balloon"
                     lazy-load="false">
            </picture>
            <div class="image-caption">
                <p>Photo by Daria Shevtsova from Pexels</p>
                <p>https://www.pexels.com/photo/woman-sitting-on-chair-beside-balloon-1391580/</p>
            </div>
        </div>
    }
}
else if (Model.Problem == "lazy-loading")
{
    ...
    else if (Model.Query == "picture")
    {
        for(var i = 1; i <= 10; i++)
        {
            <div class="image-container">
                <picture src-fragment="/images/responsive-images/woman-balloon-chair-dark-Copy@(i)"
                         sizes="480,640,800"
                         break-points="480,800"
                         extension="jpg"
                         alt="Woman sitting on a chair beside a balloon">
                </picture>
                <div class="image-caption">
                    <p>Photo by Daria Shevtsova from Pexels</p>
                    <p>https://www.pexels.com/photo/woman-sitting-on-chair-beside-balloon-1391580/</p>
                </div>
            </div>
        }
    }
}
(c) Adding the code to our razor page to make sure the picture tag is working as we intend it to.

Cleaning up

  • WebUi
    • Features
      • Images
        • ImageTagHelper.cs

The last thing we are going to do with our tag helpers is to add just a little more functionality. We are adding the ability to specify the class, id, and any other attributes that we may like to add in the future (d).

ImageTagHelper.cs

using System.Collections.Generic;
using System.Linq;
...
using System.Text.RegularExpressions;
...
namespace WebUi.Features.Images
{
    ...
    public abstract class ImageTagHelper : TagHelper
    {
        [HtmlAttributeName("attributes")] public object Attributes { get; set; }
        ...
        [HtmlAttributeName("class")] public string Class { get; set; }
        ...
        [HtmlAttributeName("id")] public string Id { get; set; }
        ...
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            ...
            if (string.IsNullOrWhiteSpace(Class) == false)
            {
                output.AddClass(Class, HtmlEncoder.Default);
            }

            if (string.IsNullOrWhiteSpace(Id) == false)
            {
                output.Attributes.Add("id", Id);
            }

            if (Attributes != null)
            {
                var attributes = Attributes.ToDictionary();
                foreach (var kvp in attributes)
                {
                    output.Attributes.Add(kvp.Key.PascalToKebabCase(), kvp.Value);
                }
            }
            ...
        }
        ...
    }
    
    public static class ImageExtensions
    {
        public static Dictionary<string, string> ToDictionary(this object obj)
        {
            if (obj == null)
            {
                return new Dictionary<string, string>();
            }
            return (from x in obj.GetType().GetProperties() select x).ToDictionary(
                x => x.Name,
                x => x.GetGetMethod().Invoke(obj, null) == null
                    ? ""
                    : x.GetGetMethod().Invoke(obj, null).ToString());
        }

        public static string PascalToKebabCase(this string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return value;
            }

            return Regex.Replace(value, "(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])", "-$1", RegexOptions.Compiled)
                .Trim()
                .ToLower();
        }
    }
}
(d) Updating our base class so that we can add a class, id, and any other attributes that we might like to add in the future.

Make sure it's working again

  • WebUi
    • Pages
      • Image-Tag-Helper.cshtml

Before we call it quits we just need to make sure that our changes are working the way we would like them to by updating one of our previous examples (e).

Image-Tag-Helper.cshtml

...
@if (Model.Problem == "resolution-switching")
{
    ...
    if (Model.Query == "different-sizes")
    {
        <div class="image-container">
            <responsive-image src-fragment="/images/responsive-images/woman-balloon-chair-dark"
                              sizes="320,480,800"
                              extension="jpg"
                              alt="Woman sitting on a chair beside a balloon"
                              lazy-load="false"
                              class="class"
                              id="id"
                              attributes="@(new {someName = "some-name"})"></responsive-image>
            <div class="image-caption">
                <p>Photo by Daria Shevtsova from Pexels</p>
                <p>https://www.pexels.com/photo/woman-sitting-on-chair-beside-balloon-1391580/</p>
            </div>
        </div>
    }
}
(e) Updating a previous example to make sure that the changes we have made are working correctly.
Exciton Interactive LLC
Advertisement