HTML/CSS: what's a better option for layout of a tree of nested elements than nested tables?

By : cletus
Source: Stackoverflow.com
Question!

Ok, I have a set of checkboxes for selecting criteria. For argument's sake, we'll say the data looks like this:

[] Vehicles
   [] Unpowered
      [] Bicycle
      [] Skateboard
   [] Powered
      [] Two-wheeled
         [] Motorcycle
         [] Scooter
      [] Four-wheeled
etc

The []s represent checkboxes.

Ignoring the obviously contrived nature of this example, the idea is this:

  • To start with, only the Vehicle checkbox is visible;
  • If the user clicks on the Vehicle checkbox is opsn up the next level (Powered, Unpowered);
  • If the user selects Powered it opens up the next level (Two-wheeled, Four-wheeled);
  • If the user then unchecks Powered, that level disappears.

Now this is relatively easy to set up with onclick's toggling the display CSS attribute between block and none.

This is currently structured on the page as:

<table>
<tr>
  <td><input type="checkbox" onclick="toggle('__Vehicles');"></td>
  <td>Vehicles
    <table id="__Vehicles">
    <tr>
      <td><input type="checkbox"></td>
      <td>Unpowered
etc

I should point out before someone asks: the reason the checkbox was put in table cell was to control formatting. It made it easy to effectively indent since everything in the next table cell would line up.

It all works fine but the table nesting gets pretty deep. I keep thinking there has to be a better way than this. It has to be able to be easily built dynamically and have good cross-browser support for formatting of the "tree".

I should also mention that jQuery is available. I'm using it for other things.

Suggestions?

Edit: Yes the checkbox styling is important as a couple of comments have noted. Also, I have posted a solution to this, based on the responses I've gotten, as an answer below (too big to add here), just for those curious to see an example.

By : cletus


Answers

If the width of the checkboxes is still an issue (after all previous answers...) you can either:

  • Put all checkboxes in a fixed with box so you know exactly where the text is beginning (and the next level checkbox)
  • Use some jquery calculations to get the width and set the margins of the other elements dynamically (seems a bit much though...)

Using the suggested nested list structure obviously.

By : jeroen


<ul>
    <li><input type="checkbox" />Vehicles <ul>
        <li><input type="checkbox" />Unpowered</li>
        <li><input type="checkbox" />Bicycle</li>
        <li><input type="checkbox" />Skateboard</li>
    </ul></li>
    <li><input type="checkbox" />Powered <ul>
        <li><input type="checkbox" />Two-wheeled <ul>
            <li><input type="checkbox" />Motorcycle</li>
            <li><input type="checkbox" />Scooter</li>
        </ul></li>
        <li><input type="checkbox" />Four-wheeled</li>
     </ul></li>
</ul>

Edit: a little css & js to show & hide nested elements (no checkboxes)

li.opened ul {
display: block;
}

li.closed ul {
    display: none;
}

and js...

$(document).ready(function() {

$('li input:checkbox').click(function () {
    $(this).parent().toggleClass('opened');
	$(this).parent().toggleClass('closed');
});

$('li').addClass('closed');
});

edit, again, because Sparr wants some better styles (assuming that the checkboxes have a style of "checkbox"

li input.checkbox { /* input:checkbox is not 100% compatible */
    width: 6px;
    margin: 0 2px;
    /* This makes 10px be the total "width" ofh the checkbox */
}

ul {
    margin: 0 0 0 10px; /* Or whatever your total checkbox width is */
    padding: 0;
}

li {
    padding: 0;
}


Wanna see some deep jQuery magic?

<ul class="tree">
  <li><input type="checkbox" name="Vehicles" checked>Vehicles
    <ul>
      <li<input type="checkbox" name="Unpowered">Unpowered
        <ul>
          <li><input type="checkbox" name="Bicycle">Bicycle</li>
          <li><input type="checkbox" name="Skateboard">Skateboard</li>
        </ul>
      </li>
      <li><input type="checkbox" name="Powered" checked>Powered
        <ul>
          <li><input type="checkbox" name="Two-wheeled">Two-wheeled
            <ul>
              <li><input type="checkbox" name="Motorcycle" checked>Motorcycle</li>
              <li><input type="checkbox" name="Scooter">Scooter</li>
            </ul>
          </li>
          <li><input type="checkbox" name="Two-Wheeled">Four-wheeled</li>
        </ul>
    </ul>
  </li>
</ul>

Note: the only decoration here is the tree class.

ul.tree {
    list-style-type: none;
    margin: 0 0 0 -22px;
    padding: 0;
}

ul.tree ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
}

ul.tree input {
    margin-right: 6px;
}

ul.tree li {
    padding: 0 0 0 22px;
    margin: 1px;
}

.closed ul {
    display: none;
}

and the magic:

$(function() {
    $("ul.tree li:has(ul) > :checkbox").click(function() {
        jQuery(this).parent().toggleClass('closed');
    }).not(":checked").parent().addClass("closed");
});

That turns the entire thing into a working opening and closing tree as you click on checkboxes. Awesome.

Thanks to davethegr8, Jonathon Sampson and others for advice.

By : cletus


This video can help you solving your question :)
By: admin