Conditional HTML Attributes using Razor
Asked Answered
D

3

111

The variable strCSSClass often has a value but sometimes is empty.

I do not want to include an empty class="" in this input element's HTML, which means if strCSSClass is empty, I don't want the class= attribute at all.

The following is one way to do a conditional HTML attribute:

<input type="text" id="@strElementID" @(CSSClass.IsEmpty() ? "" : "class=" + strCSSClass) />

Is there a more elegant way of doing this? Specifically one where I could follow the same syntax as is used in the other parts of the element: class="@strCSSClass" ?

Drypoint answered 9/11, 2011 at 7:29 Comment(0)
S
175

You didn't hear it from me, the PM for Razor, but in Razor 2 (Web Pages 2 and MVC 4) we'll have conditional attributes built into Razor (as of MVC 4 RC tested successfully), so you can write things like this:

<input type="text" id="@strElementID" class="@strCSSClass" />

If strCSSClass is null then the class attribute won't render at all.

Further Reading

Stereobate answered 9/11, 2011 at 21:8 Comment(14)
Yup, you definitely shouldn't accept mine as the answer. That said, today there is no cleaner way to do it (hence we're creating a cleaner way to do it). Probably the cleanest way to do it is to use Html.TextBox, but that has a different set of less desirable things to it. :( Glad you like what we're adding though. :)Stereobate
But how can i combine Razor attributes with other text? I need to make the following: ... id="[email protected]". I've expected something like ...id="track_2", but it generated the following output: ...id="[email protected]"...Flushing
You can force Razor to evaluate code by putting parens around it. id="track_@(track.ID)"Stereobate
You mean to tell me that the entire 'class=""' attribute disappears? Hard to believe. Razor's parser does a lot more than I thought it did.Microfiche
Only if you render something inside of the class attribute and if that something is a null value. Otherwise, it will still render.Stereobate
Added note indicating this works successfully with MVC 4. If you are on MVC 3 see my answer below.Amphetamine
I like the way it removes the spacing surrounding the attribute assignment if its not used.Ashton
@ErikPorter PLEASE DO NOT MESS WITH MY HTML!! also see #9234967 this as well for other bad behaviourIshmaelite
@Ishmaelite Have to a agree with you, IMHO there is too much magic going on here. There is a workaround for some of these cases <td @Html.Raw( someBoolean ? "rel='tooltip' title='Drilldown' data-container='.drillDown a'" : "" )> will prevent razor from trying to add it's own quotes(I think Razors "magic" is partly a security feature so you have to use Raw to circumvent it)Amphetamine
@Amphetamine Yup. Razor is meant to be fairly magical by default, but there should always be ways to avoid the magic when you want. Btw, I don't work at Microsoft anymore (as of about a year ago). :)Stereobate
@ErikPorter I've always liked Razor for it's lack of magic IMO that makes it easy to write a bunch of code and get expected results in a single pass, rather than lots of guess-check, that's relative to other past MS technologies. Hope you are enjoying your new endeavors.Amphetamine
@ErikPorter great. Please refer to this post, in addition to the ternary operator which is great, there should be a unary boolean operator that renders only if true. For example <span class="@(if(flag, "active"))"/> or anything with simpler/smarter/shorter syntax than that.Uneducated
This doesn't work for data attributes (and possibly any attribute it doesn't recognise). What you end up with is an attribute with no value. E.g. <input type="text" data-x /> , so what you end up having to do is a conditional statement with 2 blocks of almost the same markup.Pustulate
This is false. I just tried it with @disabled = null, and I get a disabled attribute without value like <select class="..." disabled id="FileType"><option value="">1st option</option>...</select>Drislane
A
141

Note you can do something like this(at least in MVC3):

<td align="left" @(isOddRow ? "class=TopBorder" : "style=border:0px") >

What I believed was razor adding quotes was actually the browser. As Rism pointed out when testing with MVC 4(I haven't tested with MVC 3 but I assume behavior hasn't changed), this actually produces class=TopBorder but browsers are able to parse this fine. The HTML parsers are somewhat forgiving on missing attribute quotes, but this can break if you have spaces or certain characters.

<td align="left" class="TopBorder" >

OR

<td align="left" style="border:0px" >

What goes wrong with providing your own quotes

If you try to use some of the usual C# conventions for nested quotes, you'll end up with more quotes than you bargained for because Razor is trying to safely escape them. For example:

<button type="button" @(true ? "style=\"border:0px\"" : string.Empty)>

This should evaluate to <button type="button" style="border:0px"> but Razor escapes all output from C# and thus produces:

style=&quot;border:0px&quot;

You will only see this if you view the response over the network. If you use an HTML inspector, often you are actually seeing the DOM, not the raw HTML. Browsers parse HTML into the DOM, and the after-parsing DOM representation already has some niceties applied. In this case the Browser sees there aren't quotes around the attribute value, adds them:

style="&quot;border:0px&quot;"

But in the DOM inspector HTML character codes display properly so you actually see:

style=""border:0px""

In Chrome, if you right-click and select Edit HTML, it switch back so you can see those nasty HTML character codes, making it clear you have real outer quotes, and HTML encoded inner quotes.

So the problem with trying to do the quoting yourself is Razor escapes these.

If you want complete control of quotes

Use Html.Raw to prevent quote escaping:

<td @Html.Raw( someBoolean ? "rel='tooltip' data-container='.drillDown a'" : "" )>

Renders as:

<td rel='tooltip' title='Drilldown' data-container='.drillDown a'>

The above is perfectly safe because I'm not outputting any HTML from a variable. The only variable involved is the ternary condition. However, beware that this last technique might expose you to certain security problems if building strings from user supplied data. E.g. if you built an attribute from data fields that originated from user supplied data, use of Html.Raw means that string could contain a premature ending of the attribute and tag, then begin a script tag that does something on behalf of the currently logged in user(possibly different than the logged in user). Maybe you have a page with a list of all users pictures and you are setting a tooltip to be the username of each person, and one users named himself '/><script>$.post('changepassword.php?password=123')</script> and now any other user who views this page has their password instantly changed to a password that the malicious user knows.

Amphetamine answered 30/4, 2012 at 23:15 Comment(13)
That is a very good point! And actually, it makes it readable and usable in most situations.Neath
That's what I was doing in the example in my question. Thanks for a more elaborate explanation and example. :)Drypoint
Beware of spaces though. "style= display : none;" renders as none;="" :="" style="display"Octopod
I think this is the exact solution for this problem +1Brackish
This makes things much readable! I agree that this is the exact solution for this problem!Campuzano
This works great for MVC 3... can't wait to upgrade to mvc 4.Cloak
So why doesn't this work re: magic double quotes <span @(true ? "class=logo-owner" : string.Empty) /> It just produces <span class=logo-owner />Allgood
@Amphetamine Hmm not for me when I run it I get <button type="button" class=btn>Ternary Without Hyphen</button> no auto-supplied double quotes.Allgood
@Amphetamine When running the fiddle by clicking your link and doing a view source. I get <button type="button" class=btn>Ternary Without Hyphen</button> <button type="button" class=btn-success>Ternary Without Hyphen</button> via view-source:dotnetfiddle.net/MvcPage/9bd10c2dc2a347be85057732abfb2237Allgood
@Allgood One button should be grey with rounded borders, and the other green with square corners. I suspect your browser's HTML inspector or some extension is messing with the display of the HTML.Amphetamine
@Amphetamine Yes that's exactly how they are. Cant understand how the browser (Chrome 40/FF33.1/IE 10) would affect anything since this is server generated markup and if so how come only those two class attributes but not for the class attribute of the ask button or even the type="button" attributes of all three buttons. Definitely a server thing IMHO since I can also RDP into a couple of Azure virtual machines dotted around the globe for the same result IE/Firefox/Chrome.Allgood
@Allgood I switched to the network tab to see exactly what was returned over the wire, and indeed I can see what you've described, the missing quotes. So chrome must be fixing the missing quotes when parsing the HTML into the DOM.Amphetamine
@Allgood Thanks rism, after discovering this, I remembered some nuances of how Chrome handles displaying character codes in the HTML inspector, and this revealed the mystery of why trying to provide your own quotes results in double quotes. I've updated the question with the new findings, which invalidated some of my previously incorrect assumptions about where the double quoting was coming from.Amphetamine
C
14

I guess a little more convenient and structured way is to use Html helper. In your view it can be look like:

@{
 var htmlAttr = new Dictionary<string, object>();
 htmlAttr.Add("id", strElementId);
 if (!CSSClass.IsEmpty())
 {
   htmlAttr.Add("class", strCSSClass);
 }
}

@* ... *@

@Html.TextBox("somename", "", htmlAttr)

If this way will be useful for you i recommend to define dictionary htmlAttr in your model so your view doesn't need any @{ } logic blocks (be more clear).

Considering answered 9/11, 2011 at 8:6 Comment(5)
-1, never recommend someone to put logic in the views. Thew views should only be responsible for rendering. Add a HtmlHelper example instead and I'll give you +1.Talon
To test things i can use code block in view - it's more quicker. And my recommendation was to move this block into model.Considering
yes, but answers should show the best practice and not the quick and dirty version which makes code maintenance a nightmare.Talon
@Talon I think Html helper here is unnecessary. see my answer.Are
+1 @Considering Your answer is inspiring. Keep up the good work. ie. with C# explicit conversion feature, we can enhance this answer even more. Not all the code had to be shaped in a certain way. There is always a better way we are not aware of. And some project are in favor of code organization. Code organization are by practice not concepts!Johnsonian

© 2022 - 2024 — McMap. All rights reserved.