Other Entries

Log

Splitting Lists into Two Columns with Javascript

Inspired by A List Apart’s article Bulleted Lists: Multi-Layered Fudge, I decided that a better way to make a two column list is with Javascript. That way, the markup doesn’t get polluted with extra non-semantic lists. All that is needed with my approach, which could be easily adapted, is a class on each list to be converted to two columns.

See it in action.

The javascript:

function twoCols(src, type)
  {
  var origList = src;

  var leftList = document.createElement(type);
  var rightList = document.createElement(type);
  var container = document.createElement('div');

  var items = origList.getElementsByTagName('LI');

  var itemsLength = items.length/2;
  for (i = 0; i < itemsLength; i++)
    {
    leftList.appendChild(items[0]);
    }

  itemsLength = items.length;
  for (i = 0; i < itemsLength; i++)
    {
    rightList.appendChild(items[0]);
    }
  container.appendChild(leftList);
  container.appendChild(rightList);

  leftList.setAttribute('class', 'left');
  rightList.setAttribute('class', 'right');
  container.setAttribute('class','twocol');
  if (document.all)
    {
    leftList.setAttribute('className', 'left');
    rightList.setAttribute('className', 'right');
    container.setAttribute('className','twocol');
    }
  if (type == 'ol')
    {
    rightList.setAttribute('start', leftList.getElementsByTagName('LI').length + 1 );
    }
  origList.parentNode.replaceChild(container, origList);
  }

function allTwoCols (whichclass, type)
  {
  var uls = document.getElementsByTagName(type);
  for (var i=0; i< uls.length; i++)
    {
    if (uls[i].getAttribute('class') == whichclass || 
        uls[i].getAttribute('className') == whichclass)
      {
      twoCols(uls[i], type.toLowerCase());
      }
    }
  }

The css:

div.twocol ul, div.twocol ol {
  float: right;
  width: 40%; 
  margin: 0;
  padding: 0;
  list-style-position: inside;
  }
div.twocol ul {
  list-style-type: square;
  }
div.twocol .left {
  float: left;
  position: relative;
  }
div.twocol {
  margin: 0;
  padding: 0;
  }

To convert your lists:

allTwoCols('two', 'UL');
allTwoCols('two', 'OL');

The css was explained well in the ALA article, so let’s take a look at the javascript. The function twoCols does the real real work in converting the list to two columns. It takes two arguments: src and type. The first, src, is a reference to the list you want to convert. The second, type, accepts to possible values: “UL” and “OL”. We start out by using the createElement method to create new elements: two new lists and a div to hold them. We then populate a new array, items, with the existing list items: var items = origList.getElementsByTagName('LI');.

The next bit of code is breaks the list items in half and appends them to the two new lists:

var itemsLength = items.length/2;
for (i = 0; i < itemsLength; i++)
  {
  leftList.appendChild(items[0]);
  }
itemsLength = items.length;
for (i = 0; i < itemsLength; i++)
  {
  rightList.appendChild(items[0]);
  }

Here’s the tricky part: we need to look at the number of items in the items array before we add them to the new lists. That’s because the appendChild method removes the item from the array. For the same reason, we use the first item in the items array (items[0]). Doing this also maintains the original order of the list items.

Initially, we set the variable itemsLength to be half the number of items because we only want the first half. We also assume that if there’s an odd number of list items, the left hand column will have the extra one. After we build the left list, we need to reset the value of itemsLength because we want to get all of the remaining list items.

Next, attach the new lists to the div:

container.appendChild(leftList);
container.appendChild(rightList);

Next, add class names to the new items. Internet Explorer requires that you use the className attribute to apply a class to an element, so we filter for IE with document.all.

leftList.setAttribute('class', 'left');
rightList.setAttribute('class', 'right');
container.setAttribute('class','twocol');
if (document.all)
  {
  leftList.setAttribute('className', 'left');
  rightList.setAttribute('className', 'right');
  container.setAttribute('className','twocol');
  }

Next, if the list is an ordered list, we need to start the right hand list with a logical number, so we add the start attribute with a value that is calculated by grabbing the number of items in the left hand list and adding one.

if (type == 'ol')
  {
  rightList.setAttribute('start', leftList.getElementsByTagName('LI').length + 1 );
  }

Then we replace the original list with the new div that we created:

origList.parentNode.replaceChild(container, origList);

The hard part’s done. The allTwoCols function loops through our document and converts all the lists that match our class and our type, either “UL” or “OL”.

The first line grabs all the lists matching the type and stuffs them into an array called “uls”. Then it loops through and compares each list’s class to the class we specified, again using an or operator to check for IE. If the class matches, we call the twoCols function.

function allTwoCols (whichclass, type)
  {
  var uls = document.getElementsByTagName(type);
  for (var i=0; i< uls.length; i++)
    {
    if (uls[i].getAttribute('class') == whichclass || 
        uls[i].getAttribute('className') == whichclass)
      {
      twoCols(uls[i], type.toLowerCase());
      }
    }
  }

Works like a charm in Safari, Firefox, and IE. It probably works in other too, I just haven’t had a chance to test it.

02/17/05 04:51PM Design Geekiness

Comments

Add a Comment

Have something to say about what I wrote here? Let’s hear it!

The Rules

Personal Information




Remember Information


Comment Preview

 

 

Recently Played on iTunes

  1. “Argument”
    Argument
    Fugazi
    16:47 02/21/05
  2. “When The Angels Sing”
    White Light, White Heat, White Trash
    Social Distortion
    16:43 02/21/05
  3. “Skink”
    Experimental Jet Set, Trash And No Star
    Sonic Youth
    16:39 02/21/05

Last 100 Songs >