31

I have a few random blocks. Whenever a block falls in the new row, I am making it look different. When the user clicks on a button, I hide few blocks by display:none, and the problem occurs. The nth-child selector also counts hidden elements.

Is there way to ignore those specific blocks, so that again every row has a different style? This is an example of a similar scenario.

$('.hide-others').click(function () {
    $('.css--all-photo').toggleClass('hidden');
})
.board-item--inner {
    height:200px;
    background:tomato;
    text-align:center;
    color:#fff;
    font-size:33px;
    margin-bottom:15px;
    border:2px solid tomato;
}
@media (min-width:768px) and (max-width:991px) {
    .board-item:nth-child(2n+1) .board-item--inner {
        border:2px solid #000;
        background:yellow;
        color:#000;
    }
}
@media (min-width:992px) and (max-width:1199px) {
  .board-item:nth-child(3n+1) .board-item--inner {
    border:2px solid #000;
    background:yellow;
    color:#000;
  }
}
@media (min-width:1200px) {
  .board-item:nth-child(4n+1) .board-item--inner {
    border:2px solid #000;
    background:yellow;
    color:#000;
  } 
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="container">
    <div class="form-group">
        <button class="btn btn-info hide-others" type="button">Hide others</button>
    </div>
    <div class="row">
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">1</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">2</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item css--all-photo">
            <div class="board-item--inner">3</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">4</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">5</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item css--all-photo">
            <div class="board-item--inner">6</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">7</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item css--all-photo">
            <div class="board-item--inner">8</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">9</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">0</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">10</div>
        </div>
    </div>
    <div>

Simply go through the snippet or EXTERNAL FIDDLE, and you'll get my question.

I am specifically looking for a pure CSS solution. Please provide a fiddle for your answers! And I cannot remove those blocks permanently, my user has the option to filter files on button click, that is why hidden and shown scenario.

Andhi Irawan
  • 456
  • 8
  • 15
Deepak Yadav
  • 6,804
  • 5
  • 31
  • 49
  • It seems in your case, you just need to color the first items in row. am I right? – Mr_Green Sep 02 '15 at 13:56
  • Yes you got it correct, actually, they are unequal height blocks, I am adding `clear:float` in every new row, to prevent it flowing to wrong direction – Deepak Yadav Sep 02 '15 at 14:01
  • What exactly do you need to hide? and what do you need to show??? – Leo Javier Sep 02 '15 at 14:04
  • You can try pseuedo classes sleleotr like this **.this-class:not(:nth-child(1)){}** – CodeRomeos Sep 02 '15 at 14:06
  • @LeoJavier suppose there are 10 photos, uploaded by userA and userB, I have button when clicked only shows UserA photos (here I'll be hidding photos uploaded by UserB using `display:none` using specific css class) – Deepak Yadav Sep 02 '15 at 14:07
  • @CodeRomeos i tried it myself, it didn't worked, so awaiting if someone shows a working demo. – Deepak Yadav Sep 02 '15 at 14:08
  • 1
    just use the user-id as a class name... and target that class to toggle the visibility – Leo Javier Sep 02 '15 at 14:26
  • It doesn't work, even if there are hidden blocks, they get counted in `nth-child(count)` – Deepak Yadav Sep 02 '15 at 14:27
  • to Leo's point. If you have `user_a` and `user_b` as class names then you can use the CSS `:nth-child` selector the way you want to. – CaldwellYSR Sep 02 '15 at 14:34
  • @CaldwellYSR can you please add a demo fiddle – Deepak Yadav Sep 02 '15 at 14:35
  • 2
    Nope... turns out I was wrong. `:nth-of-type` and `:nth-child` are going to count all of the elements of the same type regardless of whether they are hidden or not. There might not be a CSS only solution to this problem. – CaldwellYSR Sep 02 '15 at 14:46
  • 1
    I tried `.board-item:not(.hidden):nth-of-type(count) .board-item--inner` and that didn't work either. – Bmd Sep 02 '15 at 14:53
  • Would one possibility be to sort the items after each operation, ensuring that any hidden items are sent to the bottom of the element's children? It's certainly not simple, but it could work out if the other elements have any data that can be used to reposition themselves. – Katana314 Sep 02 '15 at 15:11
  • Could you hide the elements by default, and use a class to show the ones you want, and then use `nth-child` on the shown class? – sharf Sep 02 '15 at 15:15
  • @sharf by default, I have to show, both kind of images - All Photos and My Photos – Deepak Yadav Sep 03 '15 at 07:15
  • @Deepak try adding instead of class, add inline style then try to use CSS's attribute selector, like `:not([style='display: none'])`. – Vucko Sep 03 '15 at 08:26
  • @Vucko thanx bud, i tried it, but didn't worked.! – Deepak Yadav Sep 03 '15 at 10:43
  • Hi Deepak, considering you tried @Vucko 's suggestion, does this mean you're open to a solution that alters the HTML? – Michael Benjamin Sep 03 '15 at 13:44
  • @Michael_B yes, if it fixes my issue, then I'm. – Deepak Yadav Sep 03 '15 at 14:10
  • I don't understand why this has so many upvotes. You can choose not to accept the fact that there is no pure CSS solution, but *knowingly posting a duplicate* isn't going to cause one to magically appear. – BoltClock Sep 04 '15 at 04:18
  • @Michael_B: The principle is the same - the question is asking how to scope an nth selector to some arbitrary condition, which in this case happens to be "elements that are *not* hidden". There are *hundreds* of "how to select nth child without counting hidden elements" questions anyway. – BoltClock Sep 04 '15 at 15:57

4 Answers4

28

When the user clicks on a button, I hide few blocks by display:none, and the problem occurs. The nth-child selector also counts hidden elements.

Is there way to ignore those specific blocks, so that again every row has different style?

The problem is that the nth-child() selector looks at all siblings under the same parent regardless of styling. It doesn't matter that you've applied display: none because CSS doesn't remove the element from the DOM, and therefore it's still a sibling.

From the spec:

6.6.5.2. :nth-child() pseudo-class

The :nth-child(an+b) pseudo-class notation represents an element that has an+b-1 siblings before it in the document tree, for any positive integer or zero value of n, and has a parent element. (emphasis mine)

In order for the nth-child rules you've declared to work after a user clicks to hide divs, you need to remove the hidden divs from the DOM, so they no longer exist as siblings.

In your question you request a CSS-only solution. But in your comments you say that the HTML is open for changes. You also use a bit of jQuery to hide elements.

With one small line of code added to your jQuery the problem can be solved:

$('.hidden').remove();

The .remove() method takes elements (and its descendants) out of the DOM. In this case it removes all elements with a class hidden.


CORRECTION

The problem with remove() is that elements taken from the DOM with this method can't be restored, and this breaks the toggle function.

Fortunately, jQuery offers an alternative: detach().

The .detach() method is the same as .remove(), except that .detach() keeps all jQuery data associated with the removed elements. This method is useful when removed elements are to be reinserted into the DOM at a later time.

So if we replace the original code...

$('.hide-others').click(function () {
    $('.css--all-photo').toggleClass('hidden');
})

...with this code...

var divs;

$('.photos-board-item').each(function(i){
    $(this).data('initial-index', i);
});

$('.hide-others').on('click', function () {
    if(divs) {
        $(divs).appendTo('.row').each(function(){
            var oldIndex = $(this).data('initial-index');
            $('.photos-board-item').eq(oldIndex).before(this);
        });
        divs = null;
    } else {
        divs = $('.css--all-photo').detach();
    }
});

... the grid works as intended. (code credit: @JosephMarikle)

DEMO

Now, regardless of which divs or how many are hidden, they can be toggled on and off without disrupting the visual design because the nth-child selector is counting only "visible" siblings. No changes to the CSS. No changes to the HTML.

Community
  • 1
  • 1
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
2

I would do this with combination of :nth-of-type selector and small modification of your toggle function.

Basic idea is not to remove those .css--all-photo elements from the DOM but wrap them into <hidden> containers. And unwrap() to restore full set. In this case :nth-of-type will do exactly what you did with :nth-child.

var state = false;

$('.hide-others').click(function () {
    if( !state ) {
      $('.css--all-photo').wrap('<hidden>');
      state = true;
    } else {
      $('hidden').unwrap();
      state = false;
    }
})
hidden { display:none; } 

.board-item--inner {
    height:200px;
    background:tomato;
    text-align:center;
    color:#fff;
    font-size:33px;
    margin-bottom:15px;
    border:2px solid tomato;
}
@media (min-width:768px) and (max-width:991px) {
    div.board-item:nth-of-type(2n+1) .board-item--inner {
        border:2px solid #000;
        background:yellow;
        color:#000;
    }
}
@media (min-width:992px) and (max-width:1199px) {
  div.board-item:nth-of-type(3n+1) .board-item--inner {
    border:2px solid #000;
    background:yellow;
    color:#000;
  }
}
@media (min-width:1200px) {
  div.board-item:nth-of-type(4n+1) .board-item--inner {
    border:2px solid #000;
    background:yellow;
    color:#000;
  } 
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="container">
    <div class="form-group">
        <button class="btn btn-info hide-others" type="button">Hide others</button>
    </div>
    <div class="row">
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">1</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">2</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item css--all-photo">
            <div class="board-item--inner">3</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">4</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">5</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item css--all-photo">
            <div class="board-item--inner">6</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">7</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item css--all-photo">
            <div class="board-item--inner">8</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">9</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">0</div>
        </div>
        <div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 board-item photos-board-item">
            <div class="board-item--inner">10</div>
        </div>
    </div>
    <div>
c-smile
  • 26,734
  • 7
  • 59
  • 86
0

What you are looking for is nth-of-class which unfortunately does not exist! When you toggle hidden class with JS, that is the only difference between hidden and shown children, So you need to tell css to count divs of certain class, (lets say "shown" ), but unfortunately, css nth-child selector doesn't pay much attention to classes of the child. You can read more here.

Community
  • 1
  • 1
Ramtin Gh
  • 1,035
  • 2
  • 11
  • 31
-2

here an example of what I mean: https://jsfiddle.net/happy2deepak/g7gL5zfb/4/ In this case it will ignore elements with class .css--all-photo... but you may use what ever class you want to ignore

$('.hide-others').click(function () {
    $('.user-a').not('.css--all-photo').toggleClass('hidden');
})
Leo Javier
  • 1,383
  • 12
  • 19
  • thanx Leo, but i don't think, you got my problem ! i don't have problem in hidding or showing blocks, problem is that, if i hide few elements using `display:none`, `nth-child(count)` also counts hidden elements, this breaks my styling given by css and it creates a problem in layout. so I'm looking for a way to prevent it – Deepak Yadav Sep 02 '15 at 17:36