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
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>
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');
});
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();
});
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');
});
Related
Given the html and css below, is it possible to have a .child with class selected appear on top of other .child elements? I'd like if you can give an answer that would not change html structure and css position property of .child and .parent.
Also would be great to not toggle anything on parent, it is better to toggle child classes or styles, for parent it is better to set it once.
.parent {
position: absolute;
}
.child {
position: relative;
}
<div>
<div>
<div class="parent">
<div class="child selected"></div>
</div>
<div class="parent">
<div class="child"></div>
</div>
</div>
</div>
Greatly appreciate any input, thank you.
If you really want to stick to this HTML structure you could as example hide all elements (children) and show them only when they are selected.
A better solution would be having the selected class on the parent so then you could just simply give the selected parent a higher z-index.
Here you can find a snippet of how you can toggle the display without touching the HTML
// for demo purpuses
var toggleLayer = function() {
var next = $('.child.selected').removeClass('selected').closest('.parent').next();
var element = next.length ? next : $('.parent:first-child');
element.find('.child').addClass('selected')
}
.parent {
position: absolute;
}
.child {
position: relative;
display: none;
}
.selected {
display: block;
}
/* for demo purpuses */
.child {
height: 100px;
width: 100px;
line-height: 100px;
text-align: center;
background: red;
}
button {
position: fixed;
top: 120px;
left: 10px;
}
<div>
<div>
<div class="parent">
<div class="child selected">1</div>
</div>
<div class="parent">
<div class="child">2</div>
</div>
<div class="parent">
<div class="child">3</div>
</div>
<div class="parent">
<div class="child">4</div>
</div>
</div>
</div>
<!--- FOR DEMO PURPUSES --->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button onClick="toggleLayer()">Toggle layer</button>
I have a small text translated in 3 languages. I am looking to show the first text in English, and above the text to show two buttons, one that says French and one that says Italian. And I want once you click on French, to display the text in French, and the same with Italian. This means that I will have three separate divs, one for English, one for French and one for Italian. I only want to show one of them on the page, depending on which button I click. How can I do that? Thanks
All you need to do is create a relation between button and your desired div, and then show the related div only on click and hide the others, in my code you can increase and decrease the div's, no need to touch the jQuery or CSS code, just add or remove div's from HTML
$(document).ready(function(){
$('.btnWrapper button').click(function(){
$('.btnWrapper button').removeClass('active');
$(this).addClass('active');
var realtion = $(this).data('relation');
$('.translatedText>div').removeClass('active');
$('.translatedText').find('#' + realtion).addClass('active');
});
});
.btnWrapper button.active {
background-color: red;
color: #fff;
}
.translatedText > div {
display: none;
}
.translatedText > div.active {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="btnWrapper">
<button class="active" data-relation="eng">English</button>
<button data-relation="fre">French</button>
<button data-relation="ita">Italian</button>
</div>
<div class="translatedText">
<div id="eng" class="active">
I am English's Div
</div>
<div id="fre">
I am French's Div
</div>
<div id="ita">
I am Italian's Div
</div>
</div>
$(function() {
$('[data-lang="en"]').addClass('active');
$('.flag').on('click', function() {
$('[data-lang]').removeClass('active');
$('[data-lang="' + $(this).data('lang') + '"]').addClass('active');
});
});
.flag.active {
background-color: yellow;
}
.text {
display: none;
}
.text.active {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button data-lang="en" class="flag">English</button>
<button data-lang="fr" class="flag">Français</button>
<button data-lang="it" class="flag">Italiano</button>
<div data-lang="en" class="text">My text in English</div>
<div data-lang="fr" class="text">Mon texte en français</div>
<div data-lang="it" class="text">Il mio testo in italiano</div>
document.querySelector(".btn-eng").addEventListener('click', function () {
document.querySelector(".eng").classList.add("active");
document.querySelector(".french").classList.remove("active");
document.querySelector(".italian").classList.remove("active");
});
document.querySelector(".btn-french").addEventListener('click', function () {
document.querySelector(".french").classList.add("active");
document.querySelector(".eng").classList.remove("active");
document.querySelector(".italian").classList.remove("active");
});
document.querySelector(".btn-italian").addEventListener('click', function () {
document.querySelector(".italian").classList.add("active");
document.querySelector(".eng").classList.remove("active");
document.querySelector(".french").classList.remove("active");
});
*{
font-size: 1rem;
}
.container {
margin: auto;
height: 100px;
width: 250px;
}
.flex {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: 100%;
width: 100%;
}
.eng,
.french,
.italian {
display: none;
}
.active {
display: block;
}
<div class="container">
<div class="flex">
<div class="section">
<p class="eng active">Hello Developers out there!</p>
<p class="french">Bonjour les développeurs là-bas!</p>
<p class="italian">Ciao sviluppatori là fuori!</p>
</div>
<div class="button">
<button class="btn-eng">English</button>
<button class="btn-french">French</button>
<button class="btn-italian">Italian</button>
</div>
</div>
</div>
So I have an index page with some bootstrap code to make the columns resize. I want to have a navbar on the left side and the body for the rest. However, I want to have more than one navbar. I want to create a few categories across the top and depending on which one of those you choose, it will load a navbar and a default body page into the bootstrap columns. It's for a tabletop RPG that I am writing.
So if I have Character Creation, Combat, Magic, Vehicles, etc. across the top and you click on Character Creation then it will load the navbar on the left for character creation and the main body would have a default overview page. If you click a link in the navbar, I want it to change the page on the main body.
I know how to do this the slow way and create a huge number of pages with all the navbars and such already in there. If I update one section, I have to go through and update dozens of pages.
I am trying to teach myself javascript and I know that I can do it in there but I am not sure how.
I can't seem to find an example of this to figure out the coding. I assume that there would be a variable that is being changed by the navbar when you click on the navbar links? Not sure.
This is what I have for the test index page:
<!DOCTYPE html>
<html>
<head>
<title>Main Page</title>
<meta name="viewport" content="width = device-width, initial-scale = 1">
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
</head>
<body>
<h1>This is Test 4</h1>
<div class="container-fluid">
<div class="row">
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-3">
<div class="thumbnail">
<div class="caption">
<div class="load-html" id="Navbar" data-source="navBar.html"></div>
</div>
</div>
</div>
<div class="col-lg-9 col-md-9 col-sm-9 col-xs-9">
<div class="thumbnail">
<div class="caption">
<div class="load-html" id="mainBody" data-source="test3.html"></div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script>
$(function () {
$(".load-html").each(function () {
$(this).load(this.dataset.source);
});
});
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</body>
I have written a basic JS example for you below which will work ok in a small project, however it would not scale well in a larger project. You would be better learning a framework like vue.js and using the router along side it, or just using a router like the one Jon B linked you to in his comment above.
Anyway, something this will get you going. I have included a couple tricks which you will likely use in the future, for example data attributes, foreach loops and querySelectors and updating the dom. Let me know if you have any questions and enjoy Javascript :)
// Get all the nav items
var navItems = document.querySelectorAll(".nav-item");
var sideNavTitelEl = document.getElementById('side-nav-title');
var containersEls = document.querySelectorAll(".content");
// selected will be set to the nav item that was clicked on
var selected = 0;
// Add an event listener to all the nav items
navItems.forEach(addingEventListener);
function addingEventListener(item, index) {
item.addEventListener('click', function() {
// the item is what nav item we clicked on,
// lets set the side nav and main container value to be what was clicked on
sideNavTitelEl.innerHTML = item.innerHTML;
// Now lets update the main page container
selected = parseInt(item.dataset.value);
// We use the displayNone to show / hide a container.
// In a large scale project, you will want to begin to look at using a router, as this will speed
// up your site. For this example, it will work fine as is here.
// Hide and show the correct content on page
containersEls.forEach(hideContent);
});
}
function hideContent(item, index) {
if(index === selected) {
// we want to show this container
item.classList.remove('displayNone');
} else {
// hide the container
item.classList.add('displayNone');
}
}
body {
box-sizing: border-box;;
margin: 0;
padding: 0;
position: relative;
}
.top-nav {
width: 100%;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
}
.logo {
margin-left: 30px;
margin-right: auto;
}
.nav-item {
margin-left: auto;
margin-right: 30px;
}
.nav-item:hover {
cursor: pointer;
}
.side-nav {
width: 200px;
height: calc(100vh - 60px);
position: absolute;
top: 60px;
left: 0;
display: flex;
align-items: center;
flex-direction: column;
}
.main-container {
width: 200px;
height: 200px;
background-color: yellow;
margin-left: 200px;
width: calc(100% - 200px);
height: calc(100vh - 60px);
display: flex;
justify-content: center;
align-items: center;
}
.content {
width: 100%;
height: calc(100vh - 60px);
display: flex;
justify-content: center;
align-items: center;
}
.displayNone {
display: none;
}
.container-one {
background-color: red;
}
.container-two {
background-color: green;
}
.container-three {
background-color: blue;
}
<div class="top-nav">
<p class="logo">Home</p>
<p class="nav-item item-1" data-value='0'>Item 1</p>
<p class="nav-item item-2" data-value='1'>Item 2</p>
<p class="nav-item item-3" data-value='2'>Item 3</p>
</div>
<div class="side-nav">
<p id="side-nav-title">Home</p>
<p>Something</p>
<p>Something</p>
<p>Something</p>
</div>
<div class="main-container">
<div class="content container-one" data-value='1'>
<h1>Container 1</h1>
</div>
<div class="content container-two displayNone" data-value='2'>
<h1>Container 2</h1>
</div>
<div class="content container-three displayNone" data-value='3'>
<h1>Container 3</h1>
</div>
</div>
I am trying to create on hover color change of buttons using javascript code, the unclear part for me is how to set up 'this' attribute so the hovered element trigger the css part for specific button.
$('this').mouseover(function() {
$('#div').removeClass('svg-active');
$('#span').removeClass('light-blue-link');
});
$('this').mouseout(function() {
$('#div').removeClass('svg-active');
$('#span').removeClass('light-blue-link');
});
.button-outer {
margin-top: 30px;
}
.button {
height: 30px;
width: auto;
cursor: pointer;
z-index: 1;
}
.button::before {
display:inline-block;
content:'';
height: 100%;
vertical-align: middle;
}
.light-blue-link {
color: rgb(88, 202, 230);
}
span {
font-weight: 300;
transition: color 1s ease;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='button-outer'>
<div class='button'>
<div id='div' class='svg profile'></div>
<span id='span' class=''>Profile</span>
</div>
<div class='button'>
<div id='div' class='svg friends'></div>
<span id='span' class=''>Friends</span>
</div>
<div class='button'>
<div id='div' class='svg timeline'></div>
<span id='span' class=''>Timeline</span>
</div>
<div class='button'>
<div id='div' class='svg messages'></div>
<span id='span' class=''>Messages</span>
</div>
<div class='button'>
<div id='div' class='svg bookmarks'></div>
<span id='span' class=''>Bookmarks</span>
</div>
</div>
Note: First I address the JavaScript/jQuery question, but note the "However" bit at the end — you don't need them at all for this.
Instead of 'this' you want .button or div.button.
But that's not the main problem.
The main problem is that you're using the same id on more than one element. You can't do that, it's invalid, and browsers will typically use the first element and ignore the id on the other ones.
You don't need ids on those at all. Within your handlers, this will refer to the element you hooked the event on, so you can use the fact that the div and span are inside the element (via find) to find them:
$('div.button').mouseover(function() {
var $this = $(this);
$this.find('div').removeClass('svg-active');
$this.find('span').removeClass('light-blue-link');
});
$('div.button').mouseout(function() {
var $this = $(this);
$this.find('div').removeClass('svg-active');
$this.find('span').removeClass('light-blue-link');
});
Updated example:
$('div.button').mouseover(function() {
var $this = $(this);
$this.find('div').addClass('svg-active');
$this.find('span').addClass('light-blue-link');
});
$('div.button').mouseout(function() {
var $this = $(this);
$this.find('div').removeClass('svg-active');
$this.find('span').removeClass('light-blue-link');
});
.button-outer {
margin-top: 30px;
}
.button {
height: 30px;
width: auto;
cursor: pointer;
z-index: 1;
}
.button::before {
display: inline-block;
content: '';
height: 100%;
vertical-align: middle;
}
.light-blue-link {
color: rgb(88, 202, 230);
}
span {
font-weight: 300;
transition: color 1s ease;
}
<div class='button-outer'>
<div class='button'>
<div class='svg profile'></div>
<span class=''>Profile</span>
</div>
<div class='button'>
<div class='svg friends'></div>
<span class=''>Friends</span>
</div>
<div class='button'>
<div class='svg timeline'></div>
<span class=''>Timeline</span>
</div>
<div class='button'>
<div class='svg messages'></div>
<span class=''>Messages</span>
</div>
<div class='button'>
<div class='svg bookmarks'></div>
<span class=''>Bookmarks</span>
</div>
</div>
<!-- Scripts at the bottom unless you have a good reason to do something else -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I also changed the calls in your mouseover callback to addClass rather than removeClass.
Other things to consider:
You could use event delegation rather than hooking the event on the buttons directly:
$(".button-outer").on("mouseover", ".div.button", function() {
// ...
});
You could toggle a class on the button itself rather than on the things inside it, and then use structural CSS to apply the styling
However, you don't need JavaScript for this at all: Just use a div.button:hover div rule and a div.button:hover span rule:
.button-outer {
margin-top: 30px;
}
.button {
height: 30px;
width: auto;
cursor: pointer;
z-index: 1;
}
.button::before {
display: inline-block;
content: '';
height: 100%;
vertical-align: middle;
}
div.button:hover span {
color: rgb(88, 202, 230);
}
span {
font-weight: 300;
transition: color 1s ease;
}
<div class='button-outer'>
<div class='button'>
<div class='svg profile'></div>
<span class=''>Profile</span>
</div>
<div class='button'>
<div class='svg friends'></div>
<span class=''>Friends</span>
</div>
<div class='button'>
<div class='svg timeline'></div>
<span class=''>Timeline</span>
</div>
<div class='button'>
<div class='svg messages'></div>
<span class=''>Messages</span>
</div>
<div class='button'>
<div class='svg bookmarks'></div>
<span class=''>Bookmarks</span>
</div>
</div>
<!-- Scripts at the bottom unless you have a good reason to do something else -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
In jQuery, Argument as a string inside $() is a selector. It's failing due to jQuery method look out for element / tag in your DOM like below and One more below is invalid tag.
<this></this>
Try with valid selector like $('div.button')
Note: ID is for unique identifier. please use once. its not semantic if you use multiple times.
Efficient way will be from CSS. Main benefit will mouseout case will be taken care by browser.
div.button:hover {
color: blue;
font-size: 27px;
width:100px;
}
I have a grid with list items you can click on to toggle the inner content to display itself - http://jsfiddle.net/TimmyTodd/SUf5y/1/
Each list item has an overflow of visible, but the inner content gets covered up by any list item that comes after it (even with the z-index of the inner content set to be on top of "li").
Is there any way to make that inner content appear on top of all list items?
Here's my CSS:
li {
width: 40px;
height: 40px;
display: block;
float: left;
position: relative;
z-index: 1;
overflow: visible;
}
.innerContentToShow {
width: 40px;
height: 40px;
position: absolute;
z-index: 2;
top: -10px;
left: -10px;
border: 10px solid #ccc;
background-color: transparent;
}
.innerContentToShow p {
text-align: center;
font-size: 12px;
background-color: #fff;
padding: 5px;
margin-top: 28px;
}
And some of my HTML list code:
<ul>
<li>
<div class="innerContentToShow">
<p>Lorem Ipsum</p>
</div>
</li>
<li>
<div class="innerContentToShow">
<p>Lorem Ipsum</p>
</div>
</li>
<li>
<div class="innerContentToShow">
<p>Lorem Ipsum</p>
</div>
</li>
</ul>
You must set the z-index of the clicked 'li' higher and in return the former clicked 'li' back.
Here is the example: fiddle
And here the script:
var oldli;
$('li').click(
function(){
$(oldli).toggleClass('displayNone displayBlock');
$(oldli).css("z-index","1");
$(this).toggleClass('displayBlock displayNone');
$(this).css("z-index","5");
oldli=$(this);
}
);