views:

341

answers:

3

Hi, have any of you ever had the need for a nested list like this:

1. Item 1
  1.1 - Subitem 1
  1.2 - Subitem 2
  1.3 - Subitem 3
  1.4 - Subitem 4
  1.5 - Subitem 5
2. Item 2
  2.1 - Subitem 1
  2.2 - Subitem 2
  2.3 - Subitem 3
  2.4 - Subitem 4
  2.5 - Subitem 5

Well, I know I cannot achieve that with pure HTML. It would be great to use something like this and have the sublist automatically numbered:

<ol>
<li>
   Item 1
   <ol>
     <li>Subitem 1</li>
     <li>Subitem 2</li>
     <li>Subitem 3</li>
     <li>Subitem 4</li>
     <li>Subitem 5</li>
   </ol>
</li>
<li>
   Item 2
   <ol>
     <li>Subitem 1</li>
     <li>Subitem 2</li>
     <li>Subitem 3</li>
     <li>Subitem 4</li>
     <li>Subitem 5</li>
   </ol>
</li>
</ol>

Is there a solution for this using JavaScript or jQuery or something? Thank you.

+4  A: 

You can use CSS to do so:

OL { counter-reset: item }
LI { display: block }
LI:before { content: counter(item) ". - "; counter-increment: item }
LI LI:before { content: counters(item, ".") " - "; counter-increment: item }

But it requires support for counter and counters.


Edit    Here’s a jQuery approach similar to dcneiner’s but with no limitation to depth:

function foo($ol, counters) {
    counters = counters || [];
    $ol.each(function(i) {
        var $this = $(this);
        $this.children("li").each(function(i) {
            var $this = $(this);
            $this.prepend(counters.concat([i+1]).join(".") + " ");
            $this.children("ol").each(function(j) {
                foo($(this), counters.concat([i+1]));
            });
        });
    });
}
foo($("ol:not(li > ol)"));
Gumbo
Thank you! I picked the one above because of the cross-browser compatibility. Thanks.
Marcos Buarque
Like the refactoring... I am going to try to rewrite yours now... you gave me an idea...
Doug Neiner
OK, took the basic idea of your code (loved the `concat` to copy the array and add the number!) and turned it into a jQuery plugin. Its in my answer now. Thanks for taking the time to improve this!
Doug Neiner
@Marcos Buarque: I don’t see why his first verson would be more cross-browser compatible than mine.
Gumbo
Ooops, the first time you posted, it was pure CSS. Am I wrong? Anyway, thank you for all the help...
Marcos Buarque
@Marcos Buarque: Yes, it was. And then I added the jQuery example inspired by dcneiner’s first approach.
Gumbo
+4  A: 

If you want to do it cross-browser with jQuery:

$("ol#list ol").each(function(i, el){
   $(this).children().each(function(ci,cel){
      $(this).prepend('<span class="pseudo-num">' + [i + 1, ci + 1].join('.') + ' </span>');
   });
}).addClass('pseudo-processed');

And in your CSS:

ol .pseudo-num { display: none }
ol.pseudo-processed { list-style: none; padding-left: 0 }
ol.pseudo-processed .pseudo-num { display: inline; font-weight: bold }

This is for one level only. You could alter the code to create a recursive function for multiple levels.

This is setup to progressively enhance your page. Without Javascript it would fallback to normal nested numbering.

UPDATE: Thanks to @Gumbo work, I reworked this code into a recursive plugin. It would use the same CSS as in my previous example, but now it is a "full fledged" jQuery plugin with support for any depth:

$.fn.outline = function(options, counters){
    var options  = $.extend({}, $.fn.outline.defaults, options),
        counters = counters || [];

    this.each(function(){
       $(this).children('li').each(function(i){
           var ct = counters.concat([i + 1]);
           if(counters.length){
             $('<span></span>')
                .addClass(options.numberClass)
                .text(ct.join('.') + ' ')
                .prependTo(this);
           }
           $(this).children('ol').outline(options, ct);
       })
    });

    if(!counters.length) this.addClass(options.processedClass)
}

$.fn.outline.defaults = {
       numberClass: 'pseudo-num',
    processedClass: 'pseudo-processed'
}

You could then call it on a specific #id:

 $("#list").outline();

Or use @Gumbo's nice selector to apply it to all ol tags on one page:

 $("ol:not(li > ol)").outline();

And you can either override the defaults globally, or on an individual basis:

 $.fn.outline.defaults.processedClass = 'ol-ready';
 // or
 $("#list").outline({processedClass: 'ol-ready'});
Doug Neiner
Hey, thanks, this did work! The javascript solution is still better because of the lack of support for the pure-css solution in browsers.
Marcos Buarque
Hey, thanks, this was really helpful (the multi-depth code rewritten). I wonder if other webmasters have the same need for this sort of list... I believe so. Abraços.
Marcos Buarque
A: 

Neither js nor jquery but CSS:

<STYLE type="text/css">
    UL, OL { counter-reset: item }
    LI { display: block }
    LI:before { content: counters(item, "."); counter-increment: item }
</STYLE>

More here: http://www.w3.org/TR/WCAG10-CSS-TECHS/#lists

David