How to concisely create optional HTML attributes with razor view engine?
Asked Answered
B

8

44

I'm looking for a way to write the following code with less lines of code (maybe 5). I suppose I could do the same thing as the selected class but this razor syntax isn't looking pretty.

<ul>
@foreach (var mi in Model.MenuItems) {
  <li@(mi.Selected?" class=\"selected\"":null)>
  @if (string.IsNullOrEmpty(mi.Title)) {
    <a href="@mi.Href">@mi.Text</a>
  } else {
    <a href="@mi.Href" title="@mi.Title">@mi.Text</a>
  }
  </li>
}
</ul>
Brachycephalic answered 27/9, 2010 at 0:45 Comment(1)
Latest version of razor now has built in support for this. See answer below.Brachycephalic
B
59

Fixed in ASP.NET MVC 4

see http://weblogs.asp.net/jgalloway/archive/2012/02/16/asp-net-4-beta-released.aspx

Conditional attribute rendering

If you have an attribute that might be null, in the past you've needed to do a null check to avoid writing out an empty attribute, like this:

<div @{if (myClass != null) { <text>class="@myClass"</text> } }>Content</div>

Now Razor is able to handle that automatically, so you can just write out the attribute. If it's null, the attribute isn't written:

<div class="@myClass">Content</div>

So if @myClass is null, the output is just this:

<div>Content</div>
Brachycephalic answered 1/3, 2012 at 21:29 Comment(4)
You don't actually mean ASP.NET 4, you mean ASP.NET MVC 4 which at the time of writing is still in beta - ASP.NET 4 shipped with VS2010 and .NET 4.0 and does not contain conditional attribute rendering.Voiced
How do I do that if I want to add a style to other styles. Say if a bool is true then display:none for example. Or do I still need to use that ugly conditional?Facture
@ppumkin I did it like this; var trstyle = index == 0 ? "display: none;" : null;, then in the tr itself I do <tr style="@trstyle">, which makes the first row hidden (index == 0). The output is simply <tr> for the other rows. Worked beautifully. +1.Stavros
This does not work for data-xxx attributes, though. Aaargh.... Inconsistencies....Volcanic
H
41

I've come up with a chainable HtmlAttribute class and some Html Extension methods to allow the Razor syntax below:

<ul> 
    @foreach (var mi in items) { 
    <li @Html.Css("selected", mi.Selected)> 
        <a href="@mi.Href" @Html.Attr("title", mi.Title)>@mi.Text</a> 
    </li> 
    } 
</ul> 

Here is the HtmlAttribute class:

public class HtmlAttribute : IHtmlString     
{
    private string _InternalValue = String.Empty;
    private string _Seperator;

    public string Name { get; set; }
    public string Value { get; set; }
    public bool Condition { get; set; }

    public HtmlAttribute(string name)
        : this(name, null)
    {
    }

    public HtmlAttribute( string name, string seperator )
    {
        Name = name;
        _Seperator = seperator ?? " ";
    }

    public HtmlAttribute Add(string value)
    {
        return Add(value, true);
    }

    public HtmlAttribute Add(string value, bool condition)
    {
        if (!String.IsNullOrWhiteSpace(value) && condition)
            _InternalValue += value + _Seperator;

        return this;
    }

    public string ToHtmlString()
    {
        if (!String.IsNullOrWhiteSpace(_InternalValue))
            _InternalValue = String.Format("{0}=\"{1}\"", Name, _InternalValue.Substring(0, _InternalValue.Length - _Seperator.Length));
        return _InternalValue;
    }
}

Extra info: The "seperator" is used to chain together multiple values for an attribute. This can be useful for multiple css class names (use a space) or perhaps use String.Empty to build an value dependant on multiple conditions (by using the .Add() method)

And here are the Html Extension helper methods:

public static class Extensions
{
    public static HtmlAttribute Css(this HtmlHelper html, string value)
    {
        return Css(html, value, true);
    }

    public static HtmlAttribute Css(this HtmlHelper html, string value, bool condition)
    {
        return Css(html, null, value, condition);
    }

    public static HtmlAttribute Css(this HtmlHelper html, string seperator, string value, bool condition)
    {
        return new HtmlAttribute("class", seperator).Add(value, condition);
    }

    public static HtmlAttribute Attr(this HtmlHelper html, string name, string value)
    {
        return Attr(html, name, value, true);
    }

    public static HtmlAttribute Attr(this HtmlHelper html, string name, string value, bool condition)
    {
        return Attr(html, name, null, value, condition);
    }

    public static HtmlAttribute Attr(this HtmlHelper html, string name, string seperator, string value, bool condition)
    {
        return new HtmlAttribute(name, seperator).Add(value, condition);
    }
}

Let me know if they are of use.

Thanks,

Lee

Hephzipah answered 20/11, 2010 at 12:8 Comment(2)
You need to escape the values.Lute
Hi Lee, can you give me a clue on where to put these classes in an MVC project, and how/where to reference them?Epigenesis
H
12
<ul>
@foreach (var mi in Model.MenuItems) {
    <li@(mi.Selected?" class=\"selected\"":null)>
        <a href="@mi.Href" @{if(!string.IsNullOrEmpty(mi.Title)) { <text>title="@mi.Title"</text>} }>@mi.Text</a>
    </li>
}
</ul>

I haven't tested it but it parses correctly.

Hem answered 27/9, 2010 at 1:50 Comment(3)
Wow, had you taken the time too look at the error message you would have seen that all you needed to do was add a {} around the if block contents and you would have seen my code produces the exact output you were looking for...Hem
Yikes, I did "look" at the error message and tried the brackets but I must not have comprehended the message. :) Thanks for updating your post.Brachycephalic
Sorry, I know it came off as rude. Bad morning. That said, I would definitely look at Darin's html helper since that was MVC is basically all about.Hem
G
7

That would be a good candidate for custom HTML helper:

public static class HtmlExtensions
{
    public static MvcHtmlString MenuItem(this HtmlHelper htmlHelper, MenuItem mi)
    {
        var li = new TagBuilder("li");
        if (mi.Selected)
        {
            li.AddCssClass("selected");
        }
        var a = new TagBuilder("a");
        a.MergeAttribute("href", mi.Href);
        if (!string.IsNullOrEmpty(mi.Title))
        {
            a.MergeAttribute("title", mi.Title);
        }
        a.SetInnerText(mi.Text);
        return MvcHtmlString.Create(li.ToString());
    }
}

and in your view:

<ul>
@foreach (var mi in Model.MenuItems) {
    @Html.MenuItem(mi)
}
</ul>

or using DisplayTemplates you don't even need to write a loop:

<ul>
    @Html.DisplayFor(x => x.MenuItems)
</ul>
Gotthelf answered 27/9, 2010 at 6:27 Comment(1)
Darin, your code is great but somehow I feel like this is defeating the purpose of razor. Microsoft needs to address this.Brachycephalic
I
5
<ul>
@foreach (var mi in Model.MenuItems) {
  <li@(Html.Raw((mi.Selected ? " class=\"selected\"" : null))>
    <a href="@mi.Href">@mi.Text</a>
  </li>
}
</ul>
Interim answered 30/3, 2011 at 19:2 Comment(0)
M
4

class attribute would not be rendered by Razor if value is null

<a href="#nolink" class="@(categoryId == null ? "submenu-active": null)">All</a>
Maxey answered 26/9, 2013 at 9:50 Comment(0)
M
1

For the case of multiple classes I use this simple extension method:

public static MvcHtmlString If(this string text, bool condition) {
    return new MvcHtmlString(condition ? text : string.Empty);
}

And in the view:

<div class="menuitem @("active".If(Model.Active))">
Marxismleninism answered 12/11, 2014 at 14:5 Comment(0)
E
-1

It's really pretty simple and clean:

<p @(cssClass != null) ? { class="@cssClass" }> Stuff and whatnot... </p>
Equilibrist answered 21/4, 2011 at 18:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.