Sunday, April 13, 2014

Newline Madness: Justified HTML Elements Revisited

A little while ago, I wrote about my fun trying to create evenly spaced, rotated labels. As I pointed out in the post, the evenly space portion was based on a perfectly justified grid technique I found. It solved part of my problem, however, I did find that IE gave me some problems unless I added text-justify: distributed along side the text-lign: justify in the styles. With those minor changes, everything worked fine in my development environment. However, once I tried packaging up my app for production, the solution stopped working.

A little background is necessary to understand what happened. The application has a pile of template HTML which I wanted to compress down as much as possible so I added the grunt htmlmin task to my build process so the extra white space and line breaks where removed from the markup. It turns out that this spacing trick relied on the invisible text nodes created by those line breaks and indentation I used to keep the markup readable during development. Once they weren't there, the content collapsed as if the justification was not even applied.

To demonstrate, here's the CSS from the original solution modified with my IE fix:

.menu-container {
   margin: auto;
   width: 90%;
   background-color: #aaa;
   font-size: 14pt;
}

.menu-bar {
   list-style: none outside none;
   padding: 0;
   margin: 0;
}

.menu-bar > li {
   display: inline-block;
   position: relative;
   background-color: #fff;
}

.menu-justify {
   text-align: justify;
   text-justify: distribute; /* Add for IE */
   font-size: 0;             /* Remove extra whitespace */
}

/* Force line break */
.menu-justify:after {
   content: " ";
   width: 100%;
   display: inline-block;
}

.menu-justify * {
   font-size: 14pt;          /* Restore font size */
}


And here's the formatted markup with extra white space and line breaks:


<div class="menu-container">
<ul class="menu-bar menu-justify">
<li><a href="#">One</a>
</li>
<li><a href="#">Two</a>
</li>
<li><a href="#">Three</a>
</li>
<li><a href="#">Four</a>
</li>
</ul>
</div>


Which should render a menu bar with evenly space items:



Now, using comments, I can effectively remove all the extra spacing:

<div class="menu-container">
<ul class="menu-bar menu-justify"><!--
--><li><a href="#">One</a></li><!--
--><li><a href="#">Two</a></li><!--
--><li><a href="#">Three</a></li><!--
--><li><a href="#">Four</a></li><!--
--></ul>
</div>


Which renders with all the menu items collapsed together:



Oddly enough, if you're using IE, its not broken. IE is either doing something right or wrong. I'm sure a careful inspection of the specification would reveal the right answer but that's irrelevant since the browsers are behaving the way they are and I need a solution now. As much as I'd like to believe IE is right, it is different than every other browser I tested, so, unfortunately, I have to vote for wrong. After some toiling to fix it with CSS, I punted and just dropped &nbsp; after each li element and left the comments in the markup to keep it reasonably nice looking in development but allow me to simulate production:

<div class="menu-container">
<ul class="menu-bar menu-justify"><!--
--><li><a href="#">One</a></li>&nbsp;<!--
--><li><a href="#">Two</a></li>&nbsp;<!--
--><li><a href="#">Three</a></li>&nbsp;<!--
--><li><a href="#">Four</a></li>&nbsp;<!--
--></ul>
</div>


With those changes, the styles work as expected:



The good news is that one day (or now if you know your users are using the most up-to-date browser), you can use Flexbox and avoid all the extra CSS tricks to just layout exactly what you want:

.menu-bar-flex {
   list-style: none outside none;
   padding: 0;
   margin: 0;
}

.menu-bar-flex > li {
   display: inline-block;
   position: relative;
   background-color: #fff;
}

.menu-justify-flex {
   display: flex;
   flex-flow: row nowrap;
   justify-content: space-between;
}
Using the same basic markup as before with the extra white space and newlines:

<div class="menu-container">
<ul class="menu-bar-flex menu-justify-flex">
<li><a href="#">One</a>
</li>
<li><a href="#">Two</a>
</li>
<li><a href="#">Three</a>
</li>
<li><a href="#">Four</a>
</li>
</ul>
</div>


Renders exactly what I want:



And, even if you take out the white space, you get the same results:

<div class="menu-container">
<ul class="menu-bar-flex menu-justify-flex"><!--
--><li><a href="#">One</a></li><!--
--><li><a href="#">Two</a></li><!--
--><li><a href="#">Three</a></li><!--
--><li><a href="#">Four</a></li><!--
--></ul>
</div>


I dropped all this code into a jsFiddle if you'd like to tinker with it yourself. Maybe there are better ways to get the same results. I had considered using the method in Bootstrap to justify nav elements which uses the table-cell display property and looks something similar to this:

.menu-bar-table {
   list-style: none outside none;
   padding: 0;
   margin: 0;
}

.menu-justify-table > li {
   display: table-cell;
   width: 1%;
   position: relative;
   text-align: center;
}

.menu-justify-table > li > a {
   display: inline-block;
   background-color: #fff;
}




However, that solution causes the first and last child to not align to the left/right side of the container which is a requirement for this particular use case. So, for now, I'll use the original solution I found and just be aware of the markup requirements to make it work properly. However, I'll keep looking around for a solution that requires less vigilance to keep working if formatting of the markup changes.