views:

439

answers:

4

I want to make a long list short by hiding some elements in long sub-lists without adding extra processing on the server side. The problem is that the markup that comes from the server is not controlled by me and is not designed in a way to make my job easy. here is the code

<ul id="long-list">
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
  <li class="header"></li>
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
  <li class="item"></li>
  <li class="header"></li>
  <li class="item"></li>
  .
  .
  .
</ul>

As you've notices the headers are sibling to the items. Now, I want to have a jQuery code that shows only 3 items after each header and hide the rest to make the list shorter and more usable.

Here is my try which is not working:

  $('#long-list li.header ~ li.item:gt(3)').hide();
  $('#long-list li.header ~ li.item:lt(3)').show();
  $('#long-list li.header').show();

The extra feature is adding a node at bottom of each section that has hidden items, saying how many items has been hidden, say "5 more..."

+2  A: 

Might be possible to do better, but this should work:

var i = 0;
$("#long-list li.header:first").nextAll("li").each(function(){
  i = $(this).hasClass("header") ? 0 : i+1;
  $(this).toggle(i<=3);
});

(Updated to not hide items before the first header.)

svinto
Thanks a lot, but first the list can be really long that looping through each item could bring even modern browsers to knee... Second, as I've listed in the sample code, at the beginning of the list there all some items that do not belong to any headers, those should not be hidden.
Allen Bargi
I doubt you will be able to do this without looping through the items somehow. Even if you manage to write something without a explicit loop, there will still be a loop within jQuery...
svinto
One issue with using .nextAll() is that it may overstep another header if there are fewer than 3 items within a header.
Soviut
@Soviut: It's supposed to do that, though it's not supposed to take all headers at first, so there's a bug. Thanks for pointing that out.
svinto
A: 

You cannot do this without a loop in jquery.

Tony
A: 

This seems to work for me, and seems to logically follow the intent of your original attempt using just the siblings selector.

  $("li.header:first").prevAll("li.item").hide();
  $("li.header").each(function() {
 $(this).nextAll("li.item:lt(3)").show();
 $(this).nextAll("li.item:gt(3)").hide();
  });
Brian Grinstead
+1  A: 

This is how I've implemented it. Note that it has some other features like adding a node at the end of sub lists that have hidden items, saying x items are hidden and bind an onclick event to toggle hidden items.

  if ($('#list li.header')[0]) {      
      var i = -1000;
      $("#list > li").each(function(){
        if ($(this).hasClass("header")) {
          (i>3) && ($(this).before( "<li  class='more'>" + (i-3) + " more...</li>").bind("click", toggle_more() )  );
          i = 0;
        } 
        else {
          i = i+1; 
        }
        $(this).toggle(i<=3);
      });
  }

It's basically based on what svinto said above. I still look for a better performance. Anyhow, right now I'm happy with the results.

Initiating i with -1000 ensures that we'll not hide items at the top of the list that do not have any header. as soon as we reach a header we'll set the i to zero and start counting.

Allen Bargi