A Stackoverflow question: conditionally hiding zero values in a table
Today I stumbled upon this question on stackoverflow:Is there a way to hide a data cell based on a specific value using just HTML/CSS? For example I have this code:As it turns out, this is actually possible with HTML5 data attributes, the CSS<table> <caption>Test</caption> <tr> <th>Values</th> <td>$100</td> </tr> <tr> <th>Initial value</th> <td>$0</td> </tr> </table>Is there a way to hide the cells that are equal to $0 using HTML/CSS only? Let's say instead of $0 I have a variable called fee that can be a variety of values: $0, $5, $20, etc. Is there a way to check what value it is and if it is found to be $0 can I then hide it?
:before
or :after
pseudo-class, a CSS content property using a value of the type attr()
, and attribute-value selector syntax to control conditional formatting:
<!doctype html> <html> <head> <style type="text/css"> /* make the cells output the value of their data-value attribute */ td:after { content: attr(data-value); } /* hide the output if the data-value is equal to "$0" */ td[data-value="$0"]:after { content: ""; } </style> </head> <body> <table> <caption>Test</caption> <tr> <th>Values</th> <td data-value="$100"></td> </tr> <tr> <th>Initial value</th> <td data-value="$0"></td> </tr> </table> </body> </html>In summary, the ingredients of the solution are:
- Encode the cell values as a custom data attribute, for example:
data-value
. The actual cells are empty. - Make the cell value show up using the
:after
pseudo-class of thetd
element. This is done by setting the CSScontent
property to the valueattr()
. Values of this type take an attribute name between the parenthesis, so in our example this becomesattr(data-value)
. - Use the attribute-value selector syntax for conditional formatting. In our example the requirement was to "hide" the value of cells with an amount equal to
"$0"
. We can express this astd[data-value="$0"]
. And since we display the value through thecontent
property of the:after
pseudo-class, we have to add:after
to ourtd
selector and specify acontent
property of""
to override the previous rule that outputs the value usingattr()
.
Values | $100 |
---|---|
Initial value |
Browser compatibility
When I first tried to implement the idea I tested with latest chrome (41) and firefox (37) where it worked just fine. Much to my surprise and joy, it works without modification in IE8 as well! I'm so happy that I don't want to spoil it by testing other IE versions, but if anyone dares to try then I'd be grateful if you could post the result in the comments. Now personally, I'm not interested in hiding cell values. But this little trick does offer some possibilities for some basic conditional formatting.Monetary amount formatting: red vs black
Consider a balance sheet of monetary amounts. Amounts should be right aligned, and we want the positive amounts to be displayed in black, and negative amounts in red:<!doctype html> <html> <head> <style type="text/css"> /* right-align monetary amounts */ td[data-monetary-amount] { text-align: right; } /* make the cells output their value */ td[data-monetary-amount]:after { content: attr(data-monetary-amount); } /* make debit amounts show up in red */ td[data-monetary-amount^="-"]:after { color: red; } </style> </head> <body> <table border="1"> <tr> <th>Gain</th> <td data-monetary-amount="$100"></td> </tr> <tr> <th>Losst</th> <td data-monetary-amount="-$100"></td> </tr> </table> </body> </html>Note the
data-monetary-amount^="-"
syntax. This is a so-called substring matching attribute selector, which is specified in CSS 3. The comparison operator ^=
tests whether the attribute value starts with a particular string, in this case the minus sign "-"
, which indicates we have a negative amount.
CSS 3 specifies similar comparison operators for a postfix match (
$=
) and an instring or "mid" match (*=
).
The result looks something like this:
Gain | $100 |
---|---|
Loss | -$100 |
Browser compatibility
As if I hadn't been blessed enough, this solution too works in IE8 (as well as Chrome and Firefox of course). Yay!A slightly less nice solution that works in CSS 2.1
You can achieve the same effect with CSS 2.1 if you encode the value in 2 data attributes, one for the sign and one for the actual absolute amount:<!doctype html> <html> <head> <style type="text/css"> /* right-align monetary amounts */ td[data-monetary-amount] { text-align: right; } /* make the value show up */ td[data-monetary-amount]:after { content: attr(data-monetary-amount); } /* make negative amounts show up in red, prefixed by the sign */ td[data-sign="-"]:after { color: red; content: attr(data-sign) attr(data-monetary-amount); } </style> </head> <body> <table border="1"> <tr> th>Debit</th> <td data-sign="+" data-monetary-amount="$100"></td> </tr> <tr> th>Credit</th> <td data-sign="-" data-monetary-amount="$100"></td> </tr> </table> </body> </html>An interesting bit of this last example is that it shows you can compose the value of the
content
property out of multiple pieces of content, in this case, two attr()
values: attr(data-sign)
to ensure that in case of negative values, we display the minus sign, and attr(data-value)
to output the absolute value of the amount.
Locale dependent date formatting
The elements we saw in the previous example can be used for basic locale dependent date formatting. Let's keep it simple and format dates either in USA format, mon/d/yyyy, or in a format that is more easily understood outside the USA, d/mon/yyyy:<!doctype html> <html> <head> <style type="text/css"> /* year comes last */ time[datetime]:after { float: right; content: attr(datetime); } /* month and day come first */ time[datetime*="-"] { float: left; } /* Months (non-USA) */ time[datetime^="01-"]:after { content: "jan/"; } ...rules for the other months go here... time[datetime^="12-"]:after { content: "dec/"; } /* Days (non-USA) */ time[datetime$="-01"]:before { content: "1/"; } ...rules for the other days go here... time[datetime$="-31"]:before { content: "31/"; } /* Months (USA) */ *[lang="en-US"] time[datetime^="01-"]:before { content: "jan/"; } ...rules for the other months go here... *[lang="en-US"] time[datetime^="12-"]:before { content: "dec/"; } /* Days (USA) */ *[lang="en-US"] time[datetime$="-01"]:after { content: "1/"; } ...rules for the other days go here... *[lang="en-US"] time[datetime$="-31"]:after { content: "31/"; } </style> </head> <body> <table border="1"> <tr> <td lang="en-US"> <time datetime="2015"> <time datetime="04-08"/> </time> </td> </tr> <tr> <td lang="en-GB"> <time datetime="2015"> <time datetime="04-08"/> </time> </td> </tr> </table> </body> </html>This solution uses the HTML5
time
-element. The time
element can have a datetime
attribute that contains the date/time in a machine readable format, and it may contain text content, which should be a human-readable representation of the date/time.
Now, personally, I do not think the HTML5
time
element is an example of good or convenient design. At least, not from the perspective of the HTML5 author.
It is a great idea to require a machine-readable representation of the date. This potentially allows user agents to do useful things with the content. And allowing the user to manually specify the human-readable representation is also not a bad idea per se. But IMO, the
time
-element would have been much more useful if authors would be allowed to only specify the machine-readable representation of the date/time and, in absence of a manually entered human representation of the date/time, let the browser figure out how that date appears in the output in a human-readable representation. That way the browser could use information about the language of the document or document section to auto-format the date, or otherwise apply some user-preference. Another idea would be to allow the HTML author to control the output format using another attribute for a format string.
Anyway, this is not the case so we can try and see what we can do on our end. The solution above is as follows:
- In the example above, a date is expressed using two
time
elements: one for the year-part and one for the month and day parts of the date. The year-part uses a non-negative integer for thedatetime
attribute, indicating a year. The mont/day-part uses adatetime
attribute to represent a valid yearless date string. I nested thetime
element that represents the month and day part inside the one that represents the year. That said, it would have been much nicer if I could've just used a singletime
-element using a singledatetime
attribute containing all dateparts, but I couldn't figure out how to manipulate such a value with CSS. So I settled for a less optimal solution, which is certainly more verbose. At least, it does not duplicate any data, which seems a requirement that we never should let go off. - The first two CSS rules ensure that month and day appear first (using
float:left
) and the year appears last (usingfloat: right
). The first CSS rule specifies that alltime
elements having adatetime
attribute should float right. The way we set it up, this matches thetime
elements that match the year part. The second CSS rule uses the substring-matching attribute selector*=
to check if thedatetime
attribute of thetime
element contains a hyphen. Since the hyphen separates the day and month parts in the yearless date string format, this rule will match alltime
elements that represent a month/day part of a date. - The remaining rules are required for formatting the month and date parts as well as the separators. (Wich is a slash,
/
). - The prefix matching attribute selector
^=
is used to test which month is identified by the prefix of the value of thedatetime
attribute. For each month, with prefixes01
through12
, there is a rule, and itscontent
property is used to output the month abbreviation likejan
,feb
,mar
etc. - The postfix matching attribute selector
$=
is used to test which day is identified by the postfix of the value of thedatetime
attribute. For each day, with postfixes01
through31
, there is a rule, and itscontent
property is used to output the day number. - The upper set of rules matching the month-prefix and day-postfix are used to generate
:after
and:before
pseudo-classes respectively to ensure that by default, the day part is displayed before the month part. - To accommodate the USA date format, the bottom set of rules was added. These are essentially a duplication of the prefix- and postfix matching rules for the month and day part respectively, but these rules have an initial selector part like this
*[lang="en-US"]
to ensure that these rules are active only if thetime
element is contained in a section that was marked as being localized for the USA. For these rules, the month parts are used to generate:before
pseudo-classes, and the day parts are used to generate:after
pseudo-classes, thus reversing the default order of displaying the month and day part.
apr/8/2015 |
8/apr/2015 |
Browser compatibility
This solution works again fine in Chrome and Firefox, but does not render in IE8. Support for thetime
element was added in IE9, and the example works just fine there. Of course, if you really want it to work for IE8, you can, just don't use a time
element but something generic such as span
, and use a custom data-
attribute for the datetime
value, like data-datetime
or similar.
Finally...
I hope this was useful information. Personally I think we still have a long way to go before we can use a pure css solution to solve all our data formatting problems, and I believe that esp. for web-applications, programmatic solutions (either on the server or on the client side) are still essential to deliver an acceptable result.That said, every little bit of functionality in CSS can help you build a solution, even if such a solution is still controlled or generated by a programmatic backend.
Any comments and feedback are greatly appreciated.
9 comments:
One question: How can the output appear in the final webpage as selectable text? It seems after my table appears with this technique, all the text is not selectable nor copyable to other places. Not sure why that is.
@Sevron Deeps,
that's a great point! I never considered this issue - shame on me. Unfortunately it looks like the text simply not selectable because the values are rendered using pseudo elements (:after). Pseudo elements are not actually part of the DOM, and I believe that makes the browser consider the text not selectable.
Here's a source https://www.w3.org/TR/CSS2/selector.html#pseudo-elements
This is actually a major problem for my hack :(
Thanks for pointing it out. I will need to reconsider and see if I know some way out of this.
Hey Roland,
Thanks for sharing. Was looking for a way to conditionally change the background color of a div.
Hope you are doing well.
Hi Sean!
Hey, glad you found this. I hope it helps!
I'm ok. How have you been? I hope all is well :)
Warm regards,
Roland.
thanks for the information
Thanks -Issue I find is that if you apply the CSS the table "sortable" function seems to not take the values as values i.e. biggest to smallest etc. Wonder why?
@Anonymous,
where does your "sortable" function come from? It is not a standard thing. Did you get it from some framework, like jquery plugin?
Most likely reason is that it does not, by default, look at the data attribute for the value to sort. You're going to have to modify your sorting function to work with data attributes.
HTH
Mr. Bouman. Thanks for sharing this. Truly appreciated!
i will try this.
Post a Comment