Hubert Strawa
Hubert Strawa

Reputation: 43

How to show only one div at the time?

I've got three sections, inside of which there are two divs. Inside the first one I have a button and after clicking it I should have the next one opened. However, only one div should be visible at the time (so when you click the next one, previous one should be closed). And I've got this functionality, but after clicking on the button again - it doesn't close the corresponding div.

I set up an example of my problem on codepen:

https://codepen.io/hubertstrawa/pen/abOwWMJ
<section>
  <div class="product">
    <span class="btn">Show more</span>
    <p>Lorem ipsum</p>
  </div>
  <div class="product-more displayNone">
    Test
  </div>
</section>
$('.btn').click(function(e) {

  // only one div to be shown but can't be closed as well.
  $('.product-more').each(function(i, v) {
  $(this).removeClass('displayBlock');
  $(this).addClass('displayNone');
  })

  if ($(e.target).parent().next().hasClass('displayNone')) {
    $(e.target).parent().next().removeClass('displayNone');
    $(e.target).parent().next().addClass('displayBlock');
  } else {
    $(e.target).parent().next().removeClass('displayBlock');
    $(e.target).parent().next().addClass('displayNone');
  }

});

Any ideas how can I make it work?

Thank you

Upvotes: 3

Views: 726

Answers (4)

Roko C. Buljan
Roko C. Buljan

Reputation: 206028

Change a .is-open on a parent element.

<section class="product is-open">    <!-- is-open toggled by JS -->
    <div class="product-more"></div> <!-- handle children styles using CSS -->
</section>
                 .product-more { display: none; }  /* default */
.product.is-open .product-more { display: block; } /* when ancestor is .is-open*/

Use delegateTarget inside the .on() method to get back the .product delegator element

const $product = $('.product'); // Collect all current products

$product.on('click', '.btn', function(e) {

  const $thisProd = $(e.delegateTarget);          // The .product delegator
  $product.not($thisProd).removeClass('is-open'); // Handle all (but not this)
  $thisProd.toggleClass('is-open');               // Handle current

});
/* QuickReset */ * {margin: 0; box-sizing: border-box;}

.product {
  background-color: #ededed;
  width: 400px;
  margin: 0 auto;
  margin-bottom: 1rem;
}

.product-title {
  position: relative;
  padding: 1rem;
}

.product .btn {
  position: absolute;
  bottom: 0;
  right: 0;
  padding: .7rem;
  background-color: cyan;
  cursor: pointer;
}

.product-more {
  width: 100%;
  padding: 1rem;
  background-color: cyan;
  display: none; /* by default */
}

.product.is-open .product-more {
  display: block;
}
<section class="product">
  <div class="product-title">
    <p>Lorem ipsum</p>
    <span class="btn">Show more</span>
  </div>
  <div class="product-more">Test</div>
</section>

<section class="product">
  <div class="product-title">
    <p>Lorem ipsum</p>
    <span class="btn">Show more</span>
  </div>
  <div class="product-more">Test</div>
</section>

<section class="product">
  <div class="product-title">
    <p>Lorem ipsum</p>
    <span class="btn">Show more</span>
  </div>
  <div class="product-more">Test</div>
</section>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

This is preferred, since it lets you change HTML and CSS, and not worry any more about JavaScript - whilst by using .prev(), .next() or .parent() (like the other answers suggest) JS is just waiting for you to change the markup - to break.

  • No need to traverse back and forth your selectors.
  • No need for .displayNone and .displayBlock on the product-more element.

Handling dynamic .product

if your .product are dynamic elements, here's another solution to the above concept:

$('.allProducts').on('click', '.btn', function(e) {

  const $product = $(e.delegateTarget).find('.product'); // Get all .product
  const $thisProd = $(this).closest('.product'); // The closest .product ancestor
  $product.not($thisProd).removeClass('is-open'); // Handle all (but not this)
  $thisProd.toggleClass('is-open'); // Handle current

});
/* QuickReset */ * {margin: 0; box-sizing: border-box;}

.product {
  background-color: #ededed;
  width: 400px;
  margin: 0 auto;
  margin-bottom: 1rem;
}

.product-title {
  position: relative;
  padding: 1rem;
}

.product .btn {
  position: absolute;
  bottom: 0;
  right: 0;
  padding: .7rem;
  background-color: cyan;
  cursor: pointer;
}

.product-more {
  width: 100%;
  padding: 1rem;
  background-color: cyan;
  display: none; /* by default */
}

.product.is-open .product-more {
  display: block;
}
<div class="allProducts">

  <section class="product">
    <div class="product-title">
      <p>Lorem ipsum</p>
      <span class="btn">Show more</span>
    </div>
    <div class="product-more">Test</div>
  </section>

  <section class="product">
    <div class="product-title">
      <p>Lorem ipsum</p>
      <span class="btn">Show more</span>
    </div>
    <div class="product-more">Test</div>
  </section>

  <section class="product">
    <div class="product-title">
      <p>Lorem ipsum</p>
      <span class="btn">Show more</span>
    </div>
    <div class="product-more">Test</div>
  </section>

</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

Upvotes: 2

mgarcia
mgarcia

Reputation: 6325

You are hiding all the product-more sections when clicking any btn button, and then, trying to show/hide the product-more section associated with the clicked button.

So, when the section product-more is already shown and you click its btn button what happens is that you first hide the associated section and then your code checks if it is not visible and then shows its again.

One possible solution is to discard the associated product-more section when hiding. Also, as divs are shown by default, you don't need the displayBlock class.

$('.btn').click(function(e) {
    var $current = $(e.target).parent().next('.product-more');

    // Hide all sections that are not the one associated to the current button.
    $('.product-more').not($current).addClass('displayNone');

    // Show or hide current section.
    $current.toggleClass('displayNone');
});

Upvotes: 1

ROOT
ROOT

Reputation: 11622

A shorter version using jQuery would be using hide() and toggle():

$('.btn').click(function(e) {
  var more = $(e.target).parent().next() ;
  $('.product-more').not(more).hide();
  $(e.target).parent().next().toggle();
});

Upvotes: 1

Nasser Ali Karimi
Nasser Ali Karimi

Reputation: 4663

You can use the toggleClass it will detect your class and change it to another.

In your each function you just need to set all items to be hide and then it will toggle classes for current item.

Codepen

https://codepen.io/nasser-ali-karimi/pen/rNVwwLy?editors=1010

$('.btn').click(function(e) {

  // only one div to be shown but can't be closed as well.
  $('.product-more').each(function(i, v) {
    $(this).removeClass('displayBlock');
    $(this).addClass('displayNone');
  })
 $(e.target).parent().next().toggleClass('displayNone displayBlock');

});

Upvotes: 1

Related Questions