Finder-like column view from hierarchical lists with jQuery

Mac OS X's Finder features a nifty NeXT throwback - the column view. This lets you browse through a hierarchy of files in a relatively compact space, and still see your path through directory structure.

Ok already, just show me the downloads!

OS X Column View

There are a couple of jQuery plugins in the archive that claim to do this, but none really fit my core needs:

  1. The script should be unobtrusive, and let you transform a hierarchy of unordered lists of links (like a Drupal menu) into a column view, without requiring altering the underlying markup.
  2. The script shouldn't require a bunch of support files - css, images, etc.
  3. The output should work basically like a Finder list view - allow keyboard navigation with arrow keys, show when items have submenus (i.e. differentiate between "folders" and "files").

I think I've achieved two out of three - keyboard navigation doesn't seem to work in Webkit browsers (Safari and Chrome), and I got lazy and used the excellent Livequery plugin rather than rebinding events - but otherwise, it transforms this:

Into this:

Usage is pretty basic:

$('#columnized').columnview();

There are no options. The aesthetics and behavior of the menu are determined by overriding the CSS that it provides, and providing a double-click handler.

The script provides few CSS classes that can be overridden to change the way the script is displayed.

/*Top level container - set the width, height and border here*/
.containerobj {
  border: 1px solid #ccc;
  height:5em;
  overflow-x:auto;
  overflow-y:hidden;
  white-space:nowrap;
}
/*Div containing an individual level of the menu hierarchy*/
.containerobj div {
  height:100%;
  overflow-y:scroll;
  overflow-x:hidden;
  float:left;
  min-width:150px;
}
/*Link*/
.containerobj a {
  display:block;
  clear:both;
  white-space:nowrap;
}
.containerobj a canvas{
  padding-left:1em;
}
/*A bottom-level element (the furthest down in the hierarchy) is displayed as a 
link, but could be overriden*/
.containerobj .feature {
  min-width:200px;
}
.containerobj .feature a {
  white-space:normal;
}
/*If you want to display links as folders vs. files, you can apply styles to the
.hasChildMenu class*/
.containerobj .hasChildMenu {
}
.containerobj .active {
  background-color:#3671cf;
  color:#fff;
}
/*You can override the color of the triangles here*/
.containerobj .hasChildMenu .widget{
  color:black;
  float:right;
  text-decoration:none;
  font-size:0.7em;
}

I chose to include these styles in the script rather than as an external file to avoid having to reference external files and worry about their placement on the server.

I should note that instead of including images for the little triangle widgets, I'm using Canvas to draw them, where available. Where it's not (in Internet Explorer), I put in a little ASCII triangle. I have to admit I did this as much to play around with Canvas as anything else.

To actually do something with the menu, I chose to use double-clicks, in keeping with the OS X style UI. Here's a sample handler:

$('#columnize a').livequery('dblclick',function(){
  window.location = $(this).attr('href');
}

When I have time, I'll probably post up a demo page with some other examples of how to style the menu. In the meantime, you can download the script here:

For jQuery 1.2.x (requires Live Query plugin):
jquery.columnview-1.0.1.js [source]
jquery.columnview-1.0.1.min.js [minified]

For jQuery 1.3.x:
jquery.columnview-1.1.1.js [source]
jquery.columnview-1.1.1.min.js [minified]

Note: I have only tested this with jQuery 1.2.6, though I'd expect it to work with 1.3.x as well. Of course, 1.3 includes the live() method, which might be used in place of LiveQuery. For now, I'm focusing on 1.2.6, as this is what I'm running with all of my Drupal installations.

Update: I've updated the script to work with 1.3.x (tested against 1.3.2) and removed the dependency on the Live Query plugin. We're using the live() method now. Downloads added above.

I've tested this script in Safari 3.x and 4.x, Chrome, Firefox 3.x, and IE 6 and 7. As previously noted, keyboard navigation doesn't work yet in Safari and Chrome, and due to silly IE css handling, the width of submenus is fixed (via css) at 200px rather than shrinking to fit the content.

Update: The latest version of Columnview now supports jQuery version 1.2.x, 1.3.x and 1.4.x. Additionally, keyboard navigation is now available on all browsers when using jQuery 1.3 or later. The Livequery plugin is no longer required, but keyboard navigation is not supported with jQuery 1.2 (at the moment).

Update 19 April 2010: New features added to Columnview

  • Added control/command- and shift- select options. Shift-select requires jQuery 1.4.x. Multi-selection is disabled by default, but you can enable it in two ways:
    1. When calling the method: $("yourselector").columnview({multi:true});
    2. Prior to calling the columnview method:
      $.fn.columnview.defaults.multi = true;
      $("yourselector").columnview();
  • Now assigning ID of original hierarchical object to columnview object, to allow easier styling, etc. Old object is reassigned to ID-processed and hidden.
  • Fixed assignment of active/inpath classes so that only items that are currently selected have class=active.

Comments

That's some pretty pimpin' Javascript there, my friend. Nicely done!


May be I don't understand, but all the links in the third level deep, dosen't work :( like:

administer / content management / categories

The link doesn't show up on the fourth column.
I testeed in IE7 and Fierfox 3 for XP.


Yes, I noticed that I'd broken the horizontal scrolling somewhere in my development process. I've refactored the code in the 1.2.x version so that it uses absolute positioning of sub-menus rather than trying to deal with float and inline-block inconsistencies between browsers. This scrolls horizontally as expected in FF 2/3, Safari 3/4, IE 6/7. Still need to test in FF2, but I don't expect it to work any differently there.

I'm updating the 1.3.x versions shortly.

1.3.x versions are now updated as well. Hope this helps!


chris's picture

This browser is excellent!

It would be interesting to try to add some custom html to the leaf display, like how the os x finder displays file information (size, date of last modification etc) in the rightmost pane when you select a file. I tried to add some html inside the leaf <li> items, but it's not carried over.

Also, it would be an improvement if the selection of the path was encoded as an anchor, so that the selected path could persist between page reloads.


Good things to consider for the next release, or if you want to contribute a patch.

-chris


chris's picture

Hi! Can your plagin do the same with images? It will be very useful! Like something this:


@ Zebotron - there's no reason it couldn't be modified to do so, if the images were within the list elements. I purposely didn't put a lot of styling into it for this reason.


chris's picture

Any plans to make it keyboard-navigable on Webkit? I'd really love to build off of this and that would be the first thing on my list to fix, but I don't know enough about Webkit to know where to start.


@Steven - Yeah, I'd definitely like to make the keyboard navigation Webkit compatible. I'm not sure where it's broken, actually. I'll have to investigate this a little more.


chris's picture

Can it do something like this, as a checklist? www.cascadinglists.com


@obsessiveListMaker - I'm not sure what part of that app you're wanting to replicate. It would certainly be possible to add other jQuery click behaviors to the list elements after they're included in the widget, and with the livequery plugin for 1.2.x or live() for 1.3, you'd be able to add items dynamically ... I think.


chris's picture

Chris - love the code. I need to prototype multi-select miller columns (finder-like column view).

Any advice on enabling multi-select on parents and children, keeping track of selections while not viewing parent.


@alooster To enable multi-selection you'd need to refactor some of the code in the click handling function to prevent hiding lower-level elements and deselecting when the control/shift key is down, around line 97. Currently the behavior is to remove the .active class from all other elements and remove child columns. You'd also need to handle selecting items between mousedown points programmatically for shift-selecting if you wanted both contiguous and noncontiguous selection ability.

To track the selection, you have two options - the easiest would be to bind a function to whatever interface element (say a Submit button) you're using that just iterates through the elements in your menu to find the "active" class:


$(your selector).find('.active').each(function() {
// Do something with your elements here
});

The other option I can think of is to bind a handler that appends data from each click to a DOM element using the data method.

If you get a chance to work on this, I'd be interested in seeing your code and incorporating it into the main release.


chris's picture

Great plugin, but it seems to be incompatible with jQuery 1.4.x. Any chance to update it ?
Thanks.


@Matthieu, I'll have to take a look. To be honest, I've not done any work in production with 1.4 yet, as I'm doing mostly Drupal 6 (which is still tied to 1.2.x) at the moment.

Are you seeing specific issues, or is it just that it doesn't work at all?


chris's picture

Any way to set the view on load of the page?

I want to use it in a prototype and want to show a folder 2 levels deep selected already. Possible?


@Josef - I haven't tried this, but since we're binding the loading of each level in the hierarchy to the click event, you should be able to use the .trigger() method to "click" through the hierarchy programmatically on page load or by triggering another event to load the particular item you want.


chris's picture

@Matthieu - The plugin has now been updated for jQuery 1.4.x compatibility, among other fixes and improvements. See the revised links to download.


chris's picture

Was wondering if you could kindly fix the following error

Error: $(self).data("sub") is undefined
Source File: http://static.christianyates.com/columnview/jquery.columnview.js
Line: 125


@AC- Please check the latest version of the code at http://columnview.googlecode.com/


chris's picture

Chris,

zOMG this is cool. However, it looks like the entire tree must be generated in HTML first, then parsed with JS.

I've got some "trees" with upwards of 30,000 nodes an am wondering how well this performs under that sort of load. Is there any way to fetch the "subtree" stuff from the database (e.g. Controller) when clicked? Thoughts?

Thanks!!!

--
Matt


Hi, this is a good piece of code that I have been looking for. However, there are few things that I believe can make it more Finder-like:

When you come to the leaf, it should not show the leaf node again on the next panel, but instead let you open that leaf immediate. For example, click "Create content"->Map should open the Map page instead of showing Map again.

Better yet, show some information on that extra panel if possible. Then, the page "Map" can be open directly by double click. So, single click shows information, double click open the link.

The other one is, Finder only have three panels. When you come to the last panel (the right one) and click to expand a subfolder, it should replace the right panel with the new subfolder, and go on.

I hope it makes sense. It's hard for me to explain.


@Esente: You have the option to show whatever you'd like in the final panel using the preview callback. You can pull in more data using AJAX techniques, or grab elements from other places on the page. You can do whatever you want. If we're just looking at a list of links, the only data we have is the anchor itself, and any title attribute that has been added to that anchor, so that's what the plugin displays by default.

Download the latest tarball from Google Code and see some examples.

Also - the Mac OS X Finder shows as many columns as it has space for. Technically, my implementation isn't trying to replicate what Apple's Finder does - it's an implementation of the Miller Columns UI pattern. Feel free to contribute a patch to limit the number of columns displayed. That might be useful to some people.


chris's picture

First, let me echo the compliments on this script. It really works very well. Second, I had the same question as Josef and tried to use the trigger function by triggering a click event on one of the anchor tags in the first column but to no avail. Have you thought any more about this or have you already come across a solution?

Thanks again!


Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <img> <b> <blockquote> <s>
  • Lines and paragraphs break automatically.
  • Easily link to terms in various wikis. For help, see <a href="/interwiki/1">interwiki</a>.
  • Images can be added to this post.

More information about formatting options