Wrapping lists into columns

Wrap lists in columns

I'm using ColdFusion to populate a template that includes HTML unordered lists (<ul>s).

Most of these aren't that long, but a few have ridiculously long lengths and could really stand to be in 2-3 columns.

Is there an HTML, ColdFusion or perhaps JavaScript (I'm accepting jQuery solutions) way to do this easily? It's not worth some over-complicated heavyweight solution to save some scrolling.

Is this jquery plugin of any use to you?

Check out the Columnizer jQuery plugin.

13 Answers

So I dug up this article from A List Apart CSS Swag: Multi-Column Lists. I ended up using the first solution, it's not the best but the others require either using complex HTML that can't be generated dynamically, or creating a lot of custom classes, which could be done but would require loads of in-line styling and possibly a huge page.

Other solutions are still welcome though.

It's sad that two years later there is still no clean way to do this. Thanks IE.

+1 I find method 6 the cleanest approach - it can be adjusted to use no markup and very little CSS.

If Safari and Firefox support is good enough for you, there is a CSS solution:

ul {
  -webkit-column-count: 3;
     -moz-column-count: 3;
          column-count: 3;
  -webkit-column-gap: 2em;
     -moz-column-gap: 2em;
          column-gap: 2em;
}

I'm not sure about Opera.

I'm also on this solution, but I'm losing the list bullets... even resetting them or trying an image does not work. Anybody?

97.8% browser support now. Check more about columns option in CSS

Wrapping lists into columns

There is no pure CSS/HTML way to achieve this, as far as I know. Your best bet would be to do it in pre-processing (if list length > 150, split into 3 columns, else if > 70, split into 2 columns, else 1).

The other option, using JavaScript (I'm not familiar with the jQuery library specifically) would be to iterate through lists, probably based on them being a certain class, count the number of children, and if it is a high enough number, dynamically create a new list after the first, transferring some number of list items to the new list. As far as implementing the columns, you could probably float them left, followed by an element that had the style clear: left or clear: both.

.column {
  float: left;
  width: 50%;
}
.clear {
  clear: both;
}

<ul class="column">
   <li>Item 1</li>
   <li>Item 2</li>
   <!-- ... -->
   <li>Item 49</li>
   <li>Item 50</li>
</ul>
<ul class="column">
   <li>Item 51</li>
   <li>Item 52</li>
   <!-- ... -->
   <li>Item 99</li>
   <li>Item 100</li>
</ul>
<div class="clear">

I've done this with jQuery - it's cross-platform and a minimum of code.

Select the UL, clone it, and insert it after the previous UL. Something like:

$("ul#listname").clone().attr("id","listname2").after()

This will insert a copy of your list after the previous one. If the original list is styled with a float:left, they should appear side by side.

Then you can delete the even items from the left-hand list and the odd items from the right hand list.

$("ul#listname li:even").remove();
$("ul#listname2 li:odd").remove();

Now you have a left to right two column list.

To do more columns you'll want to use .slice(begin,end) and/or the :nth-child selector. ie, for 21 LIs you could .slice(8,14) to create a new UL inserted after your original UL, then select the original UL and delete the li's selected with ul :gt(8).

Try the Bibeault/Katz book on jQuery it's a great resource.

Here is a variation on Thumbkin's example (using Jquery):

var $cat_list = $('ul#catList'); // UL with all list items.
var $cat_flow = $('div#catFlow'); // Target div.
var $cat_list_clone = $cat_list.clone(); // Clone the list.
$('li:odd', $cat_list).remove(); // Remove odd list items.
$('li:even', $cat_list_clone).remove(); // Remove even list items.
$cat_flow.append($cat_list_clone); // Append the duplicate to the target div.

The following JavaScript code works only in Spidermonkey and Rhino, and it operates on E4X nodes--i.e., this is useful only for server-side JavaScript, but it might give someone a starting point for doing a jQuery version. (It's been very useful to me on the server side, but I haven't needed it on the client badly enough to actually build it.)

function columns(x,num) {
     num || (num = 2);
     x.normalize();

     var cols, i, j, col, used, left, len, islist;
     used = left = 0;
     cols = <div class={'columns cols'+num}></div>;

     if((left = x.length())==1)
          left = x.children().length();
     else
          islist = true;

     for(i=0; i<num; i++) {
          len = Math.ceil(left/(num-i));
          col = islist ? new XMLList
                       : <{x.name()}></{x.name()}>;

          if(!islist && x['@class'].toString())
               col['@class'] = x['@class'];

          for(j=used; j<len+used; j++)
               islist ? (col += x[j].copy())
                      : (col.appendChild(x.child(j).copy()));

          used += len;
          left -= len;
          cols.appendChild(<div class={'column'+(i==(num-1) ? 'collast' : '')}>{col}</div>);
     }
     return cols;
}

You call it like columns(listNode,2) for two columns, and it turns:

<ul class="foo">
   <li>a</li>
   <li>b</li>
   <li>c</li>
</ul>

into:

<div class="columns cols2">
   <div class="column">
     <ul class="foo">
       <li>a</li>
       <li>b</li>
     </ul>
   </div>
   <div class="column collast">
     <ul class="foo">
       <li>c</li>
     </ul>
   </div>
</div>

It's meant to be used with CSS like this:

div.columns {
    overflow: hidden;
    _zoom: 1;
}

div.columns div.column {
    float: left;
}

div.cols2 div.column {
    width: 47.2%;
    padding: 0 5% 0 0;
}

div.cols3 div.column {
    width: 29.8%;
    padding: 0 5% 0 0;
}

div.cols4 div.column {
    width: 21.1%;
    padding: 0 5% 0 0;
}

div.cols5 div.column {
    width: 15.9%;
    padding: 0 5% 0 0;
}

div.columns div.collast {
    padding: 0;
}

The thing that most people are forgetting is that when floating <li/> items, all of the items have to be the same height, or the columns start getting out of whack.

Since you're using a server side language, my recommendation would be to use CF to split the list into 3 arrays. Then you can use an outer ul to wrap the 3 inner ul like so:

<cfset thelist = "1,2,3,4,5,6,7,8,9,10,11,12,13">
<cfset container = []>
<cfset container[1] = []>
<cfset container[2] = []>
<cfset container[3] = []>

<cfloop list="#thelist#" index="i">
       <cfif i mod 3 eq 0>
           <cfset arrayappend(container[3], i)>
       <cfelseif i mod 2 eq 0>
           <cfset arrayappend(container[2], i)>
       <cfelse>
           <cfset arrayappend(container[1], i)>
       </cfif>
</cfloop>

<style type="text/css">
      ul li { float: left; }
      ul li ul li { clear: left; }
</style>

<cfoutput>
<ul>
       <cfloop from="1" to="3" index="a">
       <li>
           <ul>
               <cfloop array="#container[a]#" index="i">
               <li>#i#</li>
               </cfloop>
           </ul>
       </li>
       </cfloop>
</ul>
</cfoutput>

Using a modulo operation, you can quickly split your list into multiple lists by inserting a </ul><ul> during your loop.

<cfset numberOfColumns = 3 />
<cfset numberOfEntries = 34 />
<ul style="float:left;">
     <cfloop from="1" to="#numberOfEntries#" index="i">
         <li>#i#</li>
             <cfif NOT i MOD ceiling(numberOfEntries / numberOfColumns)>
                 </ul>
                 <ul style="float:left;">
             </cfif>
     </cfloop>
</ul>

Use ceiling() instead of round() to ensure that you don't have extra values at the end of the list and that the last column is shortest.

Flexbox can be used to wrap items in both row and column directions.

The main idea is to set the flex-direction on the container to either row or column.

NB: Nowadays browser support is pretty good.

FIDDLE

(Sample markup taken from this old 'list apart' article)

ol {
  display: flex;
  flex-flow: column wrap; /* flex-direction: column */
  height: 100px; /* need to specify height :-( */
}
ol ~ ol {
  flex-flow: row wrap; /* flex-direction: row */
  max-height: auto; /* override max-height of the column direction */
}
li {
  width: 150px;
}
a {
  display: inline-block;
  padding-right: 35px;
}

<p>items in column direction</p>
<ol>
  <li><a href="#">Aloe</a>
  </li>
  <li><a href="#">Bergamot</a>
  </li>
  <li><a href="#">Calendula</a>
  </li>
  <li><a href="#">Damiana</a>
  </li>
  <li><a href="#">Elderflower</a>
  </li>
  <li><a href="#">Feverfew</a>
  </li>
  <li><a href="#">Ginger</a>
  </li>
  <li><a href="#">Hops</a>
  </li>
  <li><a href="#">Iris</a>
  </li>
  <li><a href="#">Juniper</a>
  </li>
  <li><a href="#">Kava kava</a>
  </li>
  <li><a href="#">Lavender</a>
  </li>
  <li><a href="#">Marjoram</a>
  </li>
  <li><a href="#">Nutmeg</a>
  </li>
  <li><a href="#">Oregano</a>
  </li>
  <li><a href="#">Pennyroyal</a>
  </li>
</ol>
<hr/>
<p>items in row direction</p>
<ol>
  <li><a href="#">Aloe</a>
  </li>
  <li><a href="#">Bergamot</a>
  </li>
  <li><a href="#">Calendula</a>
  </li>
  <li><a href="#">Damiana</a>
  </li>
  <li><a href="#">Elderflower</a>
  </li>
  <li><a href="#">Feverfew</a>
  </li>
  <li><a href="#">Ginger</a>
  </li>
  <li><a href="#">Hops</a>
  </li>
  <li><a href="#">Iris</a>
  </li>
  <li><a href="#">Juniper</a>
  </li>
  <li><a href="#">Kava kava</a>
  </li>
  <li><a href="#">Lavender</a>
  </li>
  <li><a href="#">Marjoram</a>
  </li>
  <li><a href="#">Nutmeg</a>
  </li>
  <li><a href="#">Oregano</a>
  </li>
  <li><a href="#">Pennyroyal</a>
  </li>
</ol>

To output the list into multiple grouped tag you can loop in this fashion.

<cfset list="1,2,3,4,5,6,7,8,9,10,11,12,13,14">
<cfset numberOfColumns = "3">

<cfoutput>
<cfloop from="1" to="#numberOfColumns#" index="col">
  <ul>
  <cfloop from="#col#" to="#listLen(list)#" index="i" step="#numberOfColumns#">
    <li>#listGetAt(list,i)#</li>
  </cfloop>
  </ul>
</cfloop>
</cfoutput>

Here is another solution that allows for columned lists in the following style:

1.      4.      7.       10.
2.      5.      8.       11.
3.      6.      9.       12.

(but it's pure javascript, and requires jQuery, with no fallback)

The following contains a some code that modifies the Array prototype to give a new function called 'chunk' that breaks any given Array into chunks of a given size. Next is a function called 'buildColumns' that takes a UL selector string and a number used to designate how many rows your columns may contain. (Here is a working JSFiddle)

$(document).ready(function(){
    Array.prototype.chunk = function(chunk_size){
        var array = this,
            new_array = [],
            chunk_size = chunk_size,
            i,
            length;

        for(i = 0, length = array.length; i < length; i += chunk_size){
            new_array.push(array.slice(i, i + chunk_size));
        }
        return new_array;
    }

    function buildColumns(list, row_limit) {
        var list_items = $(list).find('li').map(function(){return this;}).get(),
        row_limit = row_limit,
        columnized_list_items = list_items.chunk(row_limit);

        $(columnized_list_items).each(function(i){
            if (i != 0){
                var item_width = $(this).outerWidth(),
                    item_height = $(this).outerHeight(),
                    top_margin = -((item_height * row_limit) + (parseInt($(this).css('margin-top')) * row_limit)),
                    left_margin = (item_width * i) + (parseInt($(this).css('margin-left')) * (i + 1));

                $(this[0]).css('margin-top', top_margin);
                $(this).css('margin-left', left_margin);
            }
        });
    }

    buildColumns('ul#some_list', 5);
});

Since I had the same problem and couldn't find anything "clean" I thought I'd posted my solution. In this example I use a reversed while loop so I can use splice instead of slice. The advantage now is splice() only needs an index and a range where slice() needs an index and the total. The latter tends to become difficult while looping.

Disadvantage is I need to reverse the stack while appending.

Example:

cols = 4; liCount = 35

for loop with slice = [0, 9]; [9, 18]; [18, 27]; [27, 35]

reversed while with splice = [27, 8]; [18, 9]; [9, 9]; [0, 9]

Code:

// @param (list): a jquery ul object
// @param (cols): amount of requested columns
function multiColumn (list, cols) {
    var children = list.children(),
        target = list.parent(),
        liCount = children.length,
        newUl = $("<ul />").addClass(list.prop("class")),
        newItems,
        avg = Math.floor(liCount / cols),
        rest = liCount % cols,
        take,
        stack = [];

    while (cols--) {
        take = rest > cols ? (avg + 1) : avg;
        liCount -= take;

        newItems = children.splice(liCount, take);
        stack.push(newUl.clone().append(newItems));
    }

    target.append(stack.reverse());
    list.remove();
}

You can try this to convert in cols.

CSS:

ul.col {
    width:50%;
    float:left;
}

div.clr {
    clear:both;
}

Html Part :

<ul class="col">
    <li>Number 1</li>
    <li>Number 2</li>

    <li>Number 19</li>
    <li>Number 20</li>
</ul>
<ul class="col">
    <li>Number 21</li>
    <li>Number 22</li>

    <li>Number 39</li>
    <li>Number 40</li>
</ul>

How do I keyboard up or down between drop-down options?

Use the keyboard to move up and down between the drop-down options

I have a custom built ajax [div] based dynamic dropdown. I have an [input] box which; onkeyup , runs an Ajax search which returns results in div s and are drawn back in using innerHTML . These div s all have highlights onmouseover so, a typical successful search yields the following structure (pardon the semi-code): [input] [div id=results] //this gets overwritten contantly by my AJAX function [div id=result1 onmouseover=highlight onclick=input.value=result1] [div id=result2 onmouseover=highlight onclick=input.value=result2] [div id=result2 onmouseover=highlight onclick=input.value=result2] [/div] It works. However, I'm missing the important functions behind regular HTML elements. I can't keyboard down or up between "options". I know javascript handles keyboard events but; I haven't been able to find a good guide. (Of course, the follow-up question will end up being: can I use…

Read more…

Http Auth in a Firefox bookmarklet

Http Auth in a Firefox bookmarklet

Http Auth in a Firefox bookmarklet I'm trying to create a bookmarklet for posting del_icio_us bookmarks to a separate account. I tested it from the command line like: wget -O - --no-check-certificate \ "https_seconduser:thepassword@api_del_icio_us/v1/posts/add?url=http_seet_dk&description=test" This works great. I then wanted to create a bookmarklet in my firefox. I googled and found bits and pieces and ended up with: javascript:void( open('https_seconduser:password@api_del_icio_us/v1/posts/add?url=' +encodeURIComponent(location.href) +'&description='+encodeURIComponent(document.title)…

Read more…

How can I turn a string of HTML into a DOM object in a FF extension?

Turn a string of HTML into a DOM object

How can I turn a string of HTML into a DOM object in a Firefox extension? I'm downloading a web page (tag soup HTML) with XMLHttpRequest and I want to take the output and turn it into a DOM object that I can then run XPATH queries on. How do I convert from a string into DOM object? It appears that the general solution is to create a hidden iframe and throw the contents of the string into that. There has been talk of updating DOMParser to support text/html but as of Firefox…

Read more…