So I needed a method to take a long, nested list and turning it into a compact, multiple acolumn list, in order to display it as sort of a site map for the home page for a site I'm working on.
Being a huge fan of jQuery, it was naturally my go-to library of choice.
Scanning the plugins site, I found a possible solution from a feller called Ingo Schommer called columnizeList.
Score, right? Well... not exactly, at least for my case.
Ingo used some of the methodoligies outlined in this article on multi-column lists on A List Apart. One of the caveats of his methodology is that each list item has to be the same height. This works Ok for a lot of use cases, but since I'm using a Drupal menu as the source for the list, it could contain arbitrary text I don't control.
So, I started from scratch. Instead of relying on consistent line heights, and applying different margin settings to list elements, I instead decided to decompose the large source list into several smaller lists (one for each column) and then use a css float
parameter to make them all appear side-by-side.
Here's a sample list for a demonstration, cribbed from Ingo's example:
(apparently a list of the most common fillers for the middle initial in Jesus H. Christ)
Anyway, here's what my script does to the above list:
And here's the code:
/* Copyright (c) 2007 Christian yates christianyates.com chris [at] christianyates [dot] com Licensed under the MIT License: http://www.opensource.org/licenses/mit-license.php Inspired by work of Ingo Schommer http://chillu.com/2007/9/30/jquery-columnizelist-plugin */ (function($){ $.fn.columnizeList = function(settings){ settings = $.extend({ cols: 3, constrainWidth: 0 }, settings); // var type=this.getNodeType(); var container = this; if (container.length == 0) { return; } var prevColNum = 10000; // Start high to avoid appending to the wrong column var size = $('li',this).size(); var percol = Math.ceil(size/settings.cols); var tag = container[0].tagName.toLowerCase(); var classN = container[0].className; var colwidth = Math.floor($(container).width()/settings.cols); var maxheight = 0; // Prevent stomping on existing ids with pseudo-random string var rand = Math.floor(Math.random().toPrecision(6)*10e6); $('<ul id="container'+rand+'" class="'+classN+'"></ul>').css({width:$(container).width()+'px'}).insertBefore(container); $('li',this).each(function(i) { var currentColNum = Math.floor(i/percol); if(prevColNum != currentColNum) { if ($("#col" + rand + "-" + prevColNum).height() > maxheight) { maxheight = $("#col" + rand + "-" + prevColNum).height(); } $("#container"+rand).append('<li class="list-column-processed"><'+tag+' id="col'+rand+'-'+currentColNum+'"></'+tag+'></li>'); } $(this).attr("value",i+1).appendTo("#col"+rand+'-'+currentColNum); prevColNum = currentColNum; }); $("li.list-column-processed").css({ 'float':'left', 'list-style':'none', 'margin':0, 'padding':0 }); if (settings.constrainWidth) { $(".list-column-processed").css({'width':colwidth + "px"}); }; $("#container"+rand).after('<div style="clear: both;"></div>'); $("#container"+rand+" "+tag).height(maxheight); // Add CSS to columns this.remove(); return this; }; })(jQuery);
There are only two parameters - cols, the number of columns to break the list into, and constrainWidth a boolean (defaulting to false) to specify whether you want all columns to be the same width.
I've tested with IE 6&7, FF3, Safari3 and Opera 9.something (for the three Opera users on the planet). The code could use a bit of refactoring perhaps for the purpose of beautification.
Update: I've added this to the jQuery Plugin site.
Comments
your wife (not verified)
Fri, 08/22/2008 - 10:02pm
Permalink
Me no understand.
Me no understand.
Mr. Webb (not verified)
Mon, 08/25/2008 - 9:46am
Permalink
Blogs are suppose to be fun
Blogs are suppose to be fun to read not hurt my brain. Ouch!
kwyjibo
Wed, 08/27/2008 - 12:41am
Permalink
What, you don't find this
What, you don't find this fun? Oh, how I pity you, Mr. Webb.
vsync (not verified)
Mon, 12/08/2008 - 4:28am
Permalink
This is very nice :) but if
This is very nice :)
but if i would like to do this, i would make it manually instead of
relying on JS code to do me the work. for small things as this manual coding
is preferred I believe.
Sjoerd (not verified)
Wed, 01/07/2009 - 5:20am
Permalink
Nice work! This helped me
Nice work! This helped me out a lot.
Two things that can be improved in my opinion.
.css({width:$(container).width()+'px'})
on the first <ul>? This will cause problems with flexible layouts when someone resizes the browser window.Why use it anyway, as <ul> is a block-level element which automatically scales to the size of its parent. If you really want to have a fixed width on this <ul> you can put it in your stylesheet, now there is no way to overwrite it.
Sjoerd (not verified)
Wed, 01/07/2009 - 6:12am
Permalink
My bad. It DOES work with
My bad. It DOES work with ordered lists. You're the man :)
Christian
Wed, 01/07/2009 - 9:11pm
Permalink
@Sjoerd - You're correct.
@Sjoerd - You're correct. The css addition is probably superfluous, and left over from an earlier iteration I can no longer remember. Taking it out doesn't change the output. Thanks for the feedback!
Anonymous Coward (not verified)
Wed, 03/18/2009 - 1:31pm
Permalink
The numbers on the above
The numbers on the above ordered list don't show on this installation of IE7. Ideas?
Christian
Wed, 03/18/2009 - 6:38pm
Permalink
Ah. I hate Internet
Ah. I hate Internet Explorer.
If you search for 'ie7 list float markers' you'll find that IE just does silly stuff when floats are involved with list elements.
I've put a hack in place here that adds a margin-left of 30px to each list element, which prevents IE from hiding them. I still need to update the jQuery plugins site.
Same Anonymous ... (not verified)
Thu, 03/19/2009 - 4:38am
Permalink
Thanks Chris. I hate IE too.
Thanks Chris. I hate IE too. Works beautifully now, well done and thanks again.
Dragan (not verified)
Wed, 06/17/2009 - 7:54pm
Permalink
Hello Chris, I really like
Hello Chris,
I really like this script. I am trying to figure out how to get rid of left margin for the first column. I tried to control it with css by setting left-margin: 0px for the ul and li but it didn't work.
Thanks in advance.
Parag Jagdale (not verified)
Thu, 07/30/2009 - 3:08pm
Permalink
Great job on the plugin.
Great job on the plugin. Only problem I found is that whenever the random id for the ul that is generated is a decimal, my whole list disappears (test by refreshing the page with the columns repeatedly).
To fix it I changed
var rand = Math.random().toPrecision(6)*10e6;
to
var rand = Math.floor(Math.random().toPrecision(6)*10e6);
It seems to work now.
Cory (not verified)
Tue, 09/29/2009 - 2:25pm
Permalink
Hi Chris, great script.
Hi Chris, great script. Works beautifully. However, I'm working on an application that will allow a user to select the number of columns they want their list to be. Some may want to default to 1 column. If I set the script to create a single column, I lose the list altogether. Any ideas?
Thanks!
hosting (not verified)
Mon, 11/30/2009 - 4:47am
Permalink
Thanks Chris.
Thanks Chris.
Chris N. (not verified)
Mon, 01/04/2010 - 10:53am
Permalink
Hi had the same behavior and
Hi had the same behavior and applied the same fix in my code. No need to share it as it has already posted by Parag here :-) This fix works fine for me. Christian, you should patch your 'official' code to avoid new users to encounter the same bug again.
Christian
Tue, 01/05/2010 - 12:10am
Permalink
Thanks for the nudge Chris.
Thanks for the nudge Chris. I've updated the code here and on the jQuery plugins page.
Mohammed Arif (not verified)
Mon, 01/11/2010 - 5:40am
Permalink
Pretty nice stuff
Pretty nice stuff Chris.
Would be better, if you can implement list sorting option also, like alpha order in the list etc in the plugin option.
Sometime columns creates alignment issues in FF 3.5.7 on the other hand it works pretty cool in IE 6.0
Good work!
-Mohammed Arif
Christian
Mon, 01/11/2010 - 10:34pm
Permalink
Thanks Mohammed. If you
Thanks Mohammed. If you could point to an example where the alignment is incorrect in FF, I'll take a look.
As for the sorting -- I'm not sure if that's something I'd include in this plugin, simply because there are probably better list-sorting plugins out there already that take into account the multiple ways you can sort a list (alpha, numeric, currency, time, etc.). You could always sort the list, then columnize it.
Andy Ford (not verified)
Thu, 02/18/2010 - 7:41pm
Permalink
On pages that didn't need
On pages that didn't need the columnizeList function I was getting an error relating to
container[0].tagName.toLowerCase();
. I suspect it's because the 'container' array was not populated with any items, so I addedif (container.length == 0) { return }
above thecontainer[0].tagName.toLowerCase();
line and that fixed the problem. Perhaps not an elegant solution but it seems to have fixed the errors. (I'm using jQuery 1.4.2)Christian
Thu, 02/18/2010 - 11:55pm
Permalink
Andy, Thanks for the note.
Andy,
Thanks for the note. I've not tested against jQuery 1.4.x yet, but will do so soon.
gkasp (not verified)
Sun, 03/07/2010 - 11:51am
Permalink
This is great but would be
This is great but would be even better if it took a "gutter" setting, which would be the space between each column. My problem is, I'm using it with an unordered list. I thought I would be able to set padding or margins. But any padding or margin I add seems to mess up the width calculations, and pushes the second column below the first.
Christian
Sun, 03/07/2010 - 11:47pm
Permalink
@gkasp When we use jQuery's
@gkasp
When we use jQuery's
.css()
method, this is essentially like adding styles inline to the element. In jquery.columnizelists.js, we're doing the following:Which jQuery injects into the selected objects as:
So following the rules of CSS precedence, the CSS that we're applying with jQuery takes precedence over those rules applied in an external stylesheet or in a
<style/>
block.In order to get around this, you can use the
!important
parameter in the property you with to override.Also, rather than overriding the individual list elements (the lowest <li>), I've refactored the plugin so that we're applying a class to the column (
.list-column-processed
) that you can add styles to as in this example. This gives you the gutter effect.Note that this refactoring is in the latest version of the code, which you can download or check out on the Google code page. The same technique works for the older version of the script, though you'll have to set up the selector differently, like
#container > li
or similar.@Andy Ford -
I re-read your post - I assume you're populating a list from the server side, but always using a static
$(<selector>).columnizeList();
in your template, which would indeed generate an error regardless of the browser or jQuery version, because you're trying to call a DOM element that doesn't exist. Your solution is about as simple as it can get, so I've included it in the latest release.gkasp (not verified)
Wed, 03/24/2010 - 7:18pm
Permalink
Why do you set the value
Why do you set the value here:
$(this).attr("value",i+1).appendTo("#col"+rand+'-'+currentColNum);
We actually use this value in an onclick event, so this was messing up our values. I can fix it, just wondering why you did this.
Christian
Wed, 03/24/2010 - 10:09pm
Permalink
@gkasp - That's the method
@gkasp - That's the method we're using to properly set the numbering of the <li> elements when this script is used on <ol>'s (ordered lists).
W3C seems to be waffling on whether the
value
andstart
attributes are deprecated or not. They deprecated both in the HTML4 spec ... only to bring them back in HTML5. However, this is the only way (that doesn't involve a bunch of css hackery) to make the columnized list appear continuously numbered (note - thestart
attribute on the <ol> element works too.If we omit these attributes, the columnized list would appear as multiple separate lists, each starting from 1. So instead of
1,2,3 || 4,5,6 || 7,8,9
you'd get
1,2,3 || 1,2,3 || 1,2,3
You should note that both specs state that the
value
should only contain an integer representing the number of the current list item. If you're stashing data there, it would be better to use jQuery's Data method. Also note thatvalue
!=$(element).val()
in this case.gkasp (not verified)
Thu, 05/06/2010 - 12:58pm
Permalink
Chris, thanks for the great
Chris, thanks for the great suggestion, I've switched to using jQuery's data().
Meanwhile, just FYI this doesn't work well with just one item in the list. I know, why would you even use it in that case ... but I was trying to be consistent in treating dynamic lists that can be from 1 to 1000 items (we switch to a scroll box when too many for columns). If you have just one, the height doesn't get set. This might not even be visibly obvious depending on how the element is used, but since we were using it to mask another object, that object behind was always shown!
Anyway, thanks so much for contributing this excellent plugin. And for replying to messages.
JR (not verified)
Wed, 07/14/2010 - 4:06pm
Permalink
First let me say that I've
First let me say that I've used this plugin a few times before and I've been very happy with the code it produces. It's always done the job well and without a lot of fuss.
I was pretty shocked to see that it has big issues when being applied to multiple lists on the same document, though. It takes the <li>s from all selected <ul>s and merges them into one big list.
I've never written a jQuery plugin before, so maybe I'm just missing something, but I went ahead and put in an each() loop so it will properly treat each matched <ul> as a separate list and do its thing for each one.
I've put the very slightly modified code on pastebin since I didn't see any mention of this being on svn or git anywhere.
Anyway, thanks again for the great work. It has certainly saved me some headaches.
JR (not verified)
Wed, 07/14/2010 - 4:29pm
Permalink
Yikes, I just noticed I left
Yikes, I just noticed I left an alert in there. Here's the fixed code: http://pastebin.com/AWTDqRSt.
Christian
Wed, 07/14/2010 - 6:07pm
Permalink
@ JR, yes, this is an
@ JR, yes, this is an excellent point. If you use a selector that matches multiple containers, I bet it makes a mess. I'll review your patch. BTW - it's in SVN here.
Anonymous Coward (not verified)
Tue, 08/24/2010 - 12:06am
Permalink
hey nice work on the script.
hey nice work on the script. works in chrome but the html5 multi column script works in chrome my problem is ie. I have yet to find a script for multi column that works in ie. heres a link to the page that doesn't work. it seems to work on your demo site in ie so can you tell me what my site has thats messing it up? thanks a million! http://futuregamespc.com/demo/few-column-list.html (the page that your script doesn't work on in ie.
Christian
Tue, 08/24/2010 - 11:45am
Permalink
I'm seeing another error on
I'm seeing another error on that page, probably before the multi-column script loads - browserdetect.js causes an error. It doesn't look related to my script as far as I can tell.
Anonymous Coward (not verified)
Tue, 08/24/2010 - 1:44pm
Permalink
oh wow i'm stupid should
oh wow i'm stupid should have looked at the console before... On the other hand though I realized IE8's console has really improved a ton! Haha thanks for the help its working great now. I love your script its the only multi column thing I have found on the internet that works in internet explorer! :D
the_light (not verified)
Tue, 09/07/2010 - 11:23am
Permalink
congrats for your plugin,
congrats for your plugin, love it!
i'm working with an horizontal scroll, so i would like to list 3 items by column, without a fixed number of them. In other words, I would like to specify rows number, not column ones. I tried to do so but was imposible to me. is your code able to do that?
thanks in advance
Stephanie Barone (not verified)
Wed, 10/13/2010 - 3:51pm
Permalink
I'm working on a
I'm working on a Drupal-based site with the Nice Menus module. I have a taxonomy generated submenu of our various departments. There are lots and lots of them. Your plug-in sounds like what I've been hunting for. Unfortunately, I'm a javascript/jquery newbie. Can you point me in the right direction in terms of basics for applying your plug-in? I have jquery.min.js linked up from the JQuery Update module. Is that enough? And where I'm really stuck is - what do I need to change in your plug-in code or my html ids and classes to target the right menu list? Thanks!
Stephanie Barone (not verified)
Thu, 10/14/2010 - 8:51am
Permalink
Figured it out by viewing
Figured it out by viewing source on this page. Easier than I thought. Now to figure out how to make it play nice with Nice Menus... Thanks for the great code!
Steven Weathers (not verified)
Tue, 01/25/2011 - 10:59am
Permalink
I recently had to format
I recently had to format data dynamically into newspaper like columns using jQuery. The problem with most scripts I encountered was that the load times varied from 30 seconds to 2 minutes due to the amount of data. Thankfully I came a crossed your plugin, which brought load times down to an average of 6-10 seconds.
Christian
Wed, 01/26/2011 - 9:14am
Permalink
Steven, I'm glad this script
Steven,
I'm glad this script works well for you, and I'm happy to hear that it delivered good performance also. To tell you the truth, I hadn't done any optimization for performance.
-c
ElP (not verified)
Fri, 03/25/2011 - 8:14am
Permalink
This works perfectly and is
This works perfectly and is easy to implement. However, I haven't been able to get it to work inside a dropdown menu. Is there any way to create a multi-column list inside a menu with this? Thanks!!
James Heil (not verified)
Fri, 04/08/2011 - 9:00pm
Permalink
I'm trying to get this script
I'm trying to get this script to work with Textpattern. Do I need to install jquery first?
Christian
Mon, 04/11/2011 - 12:18am
Permalink
@James - yes, you need to
@James - yes, you need to load jQuery prior to this script.
Reno (not verified)
Thu, 05/19/2011 - 11:53pm
Permalink
这是一个很好的插件.谢谢.
这是一个很好的插件.谢谢.
不过我还是有一些疑问.插件中的列数是固定的.如何让它不固定呢?让它随浏览器宽度而定.比如http://www.pagesthink.com/
希望你能帮上我.
I am a Chinese. English is not good. Sorry
This is a good plug-ins. Thank you.
But I still have some doubts. Plugins in the number of columns is fixed. How it is not fixed it? Let it be with the browser width. For example http://www.pagesthink.com/
I hope you can help on me.
Joenas Ejes (not verified)
Wed, 09/07/2011 - 8:31am
Permalink
Wonderful! I don't have to
Wonderful! I don't have to reinvent the wheel! Thanks mate!