views:

3740

answers:

5

I need to display a two column list of items according to the following rules:

  • Container for the columns has fluid width
  • Width of both columns needs to be equal
  • Items are dynamically rendered and at least one will be displayed
  • Item ordering needs to flow down the left column first, then the right
  • Items need to line up evenly across the bottom or in the case of an odd number the extra item should show in the left column

Here is an example:

~ Item 1   | ~ Item 6
~ Item 2   | ~ Item 7
~ Item 3   | ~ Item 8
~ Item 4   | ~ Item 9
~ Item 5   |

The HTML can be anything as long as it solves this problem. I'm restricted to using XSLT to wrap HTML around what the server spits out. I have access to two XSLT parameters: one that tells me the current item number and one that tells me how many items there are.

My CSS skills are basic/intermediate and I don't know where to start here. Any ideas on whether this is achievable and how to do it?

Update:

Thanks for the answers. Consensus seems to be either use the A List Apart article or a table which I'd prefer as it's simpler. The problem with the table is that the server gives me the items in sorted order. To use a table would mean XSLT trickery to re-sort, wouldn't it?

<tr>
    <td>Item 1</td>
    <td>Item 4</td>
</tr>
<tr>
    <td>Item 2</td>
    <td>Item 5</td>
</tr>
<tr>
    <td>Item 3</td>
    <td>&nbsp;</td>
</tr>
+2  A: 

There's a useful article on alistapart about exactly that, try taking a look there.

Update after edit:

Since you're in a situation where all you've got access to is the number of items and the current item, I think a two-column table might be your best bet.

Dominic Rodger
-1 Doesn't fit "Item ordering needs to flow down the left column first, then the right"
Aaron Digulla
See my edit. This is basically another way to avoid using a table - at all cost.
Aaron Digulla
@Aaron Digulla - maybe I'm missing something but I can't see how the article doesn't fit that requirement, yes it requires knowing in advance the items in the list, but that's a constraint on whether it's possible in CSS. Regardless, I feel like that link is a reasonable response to the question.
Dominic Rodger
Used the ALA article which worked well enough for me.
Alex Angas
+1  A: 

CSS can't do this. You need to collect the list on the server and distribute the items in two table columns (you can use CSS to make the two columns the same width).

Note: There are browser extensions for multi column layout but they are not portable.

[EDIT] I'm aware of the article on alistapart but I can't see a difference to my solution: The proposed multi column layout gives each item a fixed position (by giving each item a unique CSS ID and then positioning it somehow). This means you need to generate the HTML and sort the items on the server and then use lots of tricks to position them.

It's much more simple to use a table with two columns and drop the items into them during rendering.

Aaron Digulla
possible with css3 but obviously the browser support just isn't broad enoguh
Jonathan Fingland
+1 for a more general solution that the alistapart article
Dominic Rodger
A: 

One of your criteria is reading the list down, so a simple solution is a two-column table (@Aaron Digulla), with the first half of your list in the first column, etc. You can use any HTML, right? We've resorted to this method for some of our lists, since we didn't want to edit our CSS every time a list changed (nor did we want to remember to edit the classes in the HTML).

Method 1 in the ALA article would be the 2nd simplest solution (and more ideal from a purist point of view). Since you know the current item number and how many items there are, you could add some logic to get list items in the correct order.

CSS can easily style either method.

Michael Hessling
A: 

If you can divide the list using little javascript (which I am not good at), then this should work:

<script type="text/javascript">
var i=0;
var n=4;
if (i==0)
  {
document.write("<div id='listFlow'><ul>");
}
for (i=1;i<=15;i++)
{
if (i==n)
  {
document.write("<li>The number is " + i + "</li>" +"</ul><ul>");
var n=n+4;
  continue;
  }
document.write("<li>The number is " + i + "</li>");
}
document.write("</ul></div>");
</script>

<style type="text/css">
#listFlow ul{
float:left;
width:200px;
margin:0 0 0 10px;
padding:0;
}

#listFlow ul li{
list-style-position:inside;
list-style-type:disc;
margin:0;
padding:0;
width:180px;
}
</style>

Note: Please look at the CSS and NOT the javascript. This is just to show that how the lists can flow as described.

pinxi
+2  A: 

I know that people dismiss HTML table based layouts, but what the heck. They work. If you are vain, you are free to go the extra mile and find a pure CSS based way to do it. :-)

So here goes an XSLT solution.

<xml>
  <item id="1">Item 1</item>
  <item id="2">Item 2</item>
  <item id="3">Item 3</item>
  <item id="4">Item 4</item>
  <item id="5">Item 5</item>
  <item id="6">Item 6</item>
  <item id="7">Item 7</item>
  <item id="8">Item 8</item>
  <item id="9">Item 9</item>
</xml>

With this XSL 1.0 template applied (the number of columns is even configurable):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

  <xsl:output method="html" indent="yes" omit-xml-declaration="yes" />

  <xsl:template match="/xml">
    <xsl:variable name="vCols"  select="2" />
    <xsl:variable name="vCount" select="count(item)" />
    <xsl:variable name="vRows"  select="ceiling($vCount div $vCols)" />
    <xsl:variable name="vIterC" select="item[position() &lt;= $vCols]" />
    <xsl:variable name="vIterR" select="item[position() &lt;= $vRows]" />
    <xsl:variable name="vSelf"  select="." />

    <table>
      <xsl:for-each select="$vIterR">
        <xsl:variable name="vRowIdx" select="position()" />
        <tr>
          <xsl:for-each select="$vIterC">
            <xsl:variable name="vOffset" select="$vRows * (position() - 1)" />
            <td>
              <xsl:value-of select="$vSelf/item[$vRowIdx + $vOffset]" />
            </td>
          </xsl:for-each>
        </tr>
      </xsl:for-each>
    </table>
  </xsl:template>

</xsl:stylesheet>

Yields:

<table>
  <tr>
    <td>Item 1</td>
    <td>Item 6</td>
  </tr>
  <tr>
    <td>Item 2</td>
    <td>Item 7</td>
  </tr>
  <tr>
    <td>Item 3</td>
    <td>Item 8</td>
  </tr>
  <tr>
    <td>Item 4</td>
    <td>Item 9</td>
  </tr>
  <tr>
    <td>Item 5</td>
    <td></td>
  </tr>
</table>

In case there is only one item, it produces:

<table>
  <tr>
    <td>Item 1</td>
  </tr>
</table>

So no two columns in this situation.

In any case the table will always be well-formed (e.g. no jagged rows). The one thing that this solution does not do is sorting the output. The output will always be in document order. You seem to get the items sorted properly, so this should not be a big problem.

Tomalak