Reputation: 43
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
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.
.displayNone
and .displayBlock
on the product-more element. .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
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 div
s 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
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
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