I would like to hide a dropdown after I select something from it, but keep it there for me to hover over again later.
My current code:
<div class="navbutton_container">
<button onclick="someFunction()"><p>Title</p></button>
<div class="nav_dropdown">
<button onclick="someFunction()"><p>Content1</p></button>
<button onclick="someFunction()"><p>Content2</p></button>
<button onclick="someFunction()"><p>Content3</p></button>
<button onclick="someFunction()"><p>Content4</p></button>
</div>
</div>
.navbutton_container > .nav_dropdown {
display: none;
opacity: 0;
}
.navbutton_container:hover > .nav_dropdown {
display: block;
opacity: 1;
position: absolute;
}
After I select any button in this, I would like the dropdown to temporarily disappear so it's out of the way until they stop hovering over it. After they move off, I would like it so they could move back on and have the :hover property to work again.
If you think there's a better structure, I'm 100% open to changing it!
Here's a method that will use a combination of adding classes and detecting mouseouts to acheive the goal
function someFunction() {}
window.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.navbutton_container').forEach(el => el.addEventListener('mouseover', e => {
let targ = e.target.classList.contains('navbutton_container') ? e.target : e.target.closest('.navbutton_container');
targ.querySelector('.nav_dropdown').classList.remove('hold');
}))
document.addEventListener('click', e => {
if (e.target.closest('.nav_dropdown')) {
e.target.closest('.nav_dropdown').classList.add('hold')
}
})
})
.navbutton_container>.nav_dropdown {
display: none;
opacity: 0;
}
.navbutton_container:hover>.nav_dropdown {
display: block;
opacity: 1;
position: absolute;
}
.navbutton_container:hover>.nav_dropdown.hold {
display: none;
opacity: 0;
}
<div class="navbutton_container">
<button onclick="someFunction()" class='toggler'><p>Title</p></button>
<div class="nav_dropdown ">
<button onclick="someFunction()"><p>Content1</p></button>
<button onclick="someFunction()"><p>Content2</p></button>
<button onclick="someFunction()"><p>Content3</p></button>
<button onclick="someFunction()"><p>Content4</p></button>
</div>
</div>
<div class="navbutton_container" style='margin-top:100px'>
<button onclick="someFunction()" class='toggler'><p>Title</p></button>
<div class="nav_dropdown ">
<button onclick="someFunction()"><p>Content1</p></button>
<button onclick="someFunction()"><p>Content2</p></button>
<button onclick="someFunction()"><p>Content3</p></button>
<button onclick="someFunction()"><p>Content4</p></button>
</div>
</div>
Related
I am trying to make a "meet the staff" section that has hidden bios that display on click. Right now the div displays as it should, but only disappears when the original button is clicked again. I am needing some additional javascript to hide any opened divs when a different (or same) button is clicked. I don't know enough javascript to know what to try in order to make this happen. Thanks in advance!
HTML
<div id="lastname" class="toggle-div" style="display: none;">
<div><p>bio</p>
</div>
</div>
<button class="bio-button" onclick="myBiof()">Click for Bio</button>
Javascript
<script>
function myBiof() {
var y = document.getElementById("lastname");
if (y.style.display === "block") {
y.style.display = "none";
} else {
y.style.display = "block";
}
}
</script>
You will need to add some attributes to your HTML to keep track of which item is active, what item a button controls and which ones should be hidden from screen readers. aria-controls aria-expanded and aria-hidden do just that. Once a button is clicked... if it is currently open, just close it (remove active) and toggle the appropriate attributes. If it is not open, close all of them (remove active), open the one you clicked on (add active) and toggle the appropriate attributes. Here is a simple example:
const buttons = document.querySelectorAll("button");
const people = document.querySelectorAll(".person");
const handleClick = (event) => {
const clickedBtn = event.target;
if (clickedBtn.getAttribute("aria-expanded") === "true") {
let personId = clickedBtn.getAttribute("aria-controls");
let person = document.getElementById(personId);
person.classList.remove("active");
person.setAttribute("aria-hidden", "true");
clickedBtn.setAttribute("aria-expanded", "false");
} else if (clickedBtn.getAttribute("aria-expanded") === "false") {
people.forEach(person => {
person.classList.remove("active")
person.setAttribute("aria-hidden", "true");
});
buttons.forEach(button => button.setAttribute("aria-expanded", "false"));
let personId = clickedBtn.getAttribute("aria-controls");
let person = document.getElementById(personId);
person.classList.add("active");
person.setAttribute("aria-hidden", "false");
clickedBtn.setAttribute("aria-expanded", "true");
}
}
buttons.forEach(button => button.addEventListener("click", handleClick));
button {
display: block;
background: transparent;
border: none;
border-bottom: 1px solid #000;
width: 100%;
height: 2rem;
}
.person-container {
width: 400px;
margin: 0 auto;
}
.person {
display: none;
border-left: 1px solid #000;
border-right: 1px solid #000;
border-bottom: 1px solid #000;
padding: 1rem;
}
.person h2 {
margin-top: 0px;
}
.person p {
margin-bottom: 0px;
}
.active {
display: block;
}
<div class="person-container">
<button aria-controls="person-one" aria-expanded="false">Show Person One</button>
<div id="person-one" aria-hidden="true" class="person">
<h2>Name One</h2>
<p>Person One Bio</p>
</div>
<button aria-controls="person-two" aria-expanded="false">Show Person Two</button>
<div id="person-two" aria-hidden="true" class="person">
<h2>Name Two</h2>
<p>Person Two Bio</p>
</div>
<button aria-controls="person-three" aria-expanded="false">Show Person Three</button>
<div id="person-three" aria-hidden="true" class="person">
<h2>Name Three</h2>
<p>Person Three Bio</p>
</div>
</div>
/*
Function to add all the events to the buttons.
Checking if divs are hidden or not with [data-hidden] attribute.
This HMTML attributes can be named however you want but starting
with data-
Note that this code will only work if every button
is placed in the HTML after the bio div
*/
function addEventsAndListenToThem() {
const buttons = document.querySelectorAll('.bio-button')
buttons.forEach(btn => {
btn.onclick = (e) => {
const target = e.target.previousElementSibling
// If element is hided, show it changing
// attribute data-hidden value to false
target.getAttribute('data-hidden') === 'true' ?
target.setAttribute('data-hidden', 'false') :
target.setAttribute('data-hidden', 'true')
}
})
const hide_or_show_all = document.querySelector('.bio-button-all')
// Var to check wether .bio-button-all
// has been pressed or not
var showing = false
hide_or_show_all.onclick = () => {
// Get al divs with data-hidden property
const all_bios = document.querySelectorAll('div[data-hidden]')
showing === false ? (
() => {
// Show all divs
all_bios.forEach(bio => bio.setAttribute('data-hidden', 'false'))
showing = true
}
)() :
(
// Hide all divs
() => {
all_bios.forEach(bio => bio.setAttribute('data-hidden', 'true'))
showing = false
}
)()
}
}
addEventsAndListenToThem()
/*
Display none only to [data-hidden="true"] elements
*/
[data-hidden="true"] {
display: none;
}
.bio-button,
.bio-button-all {
display: block;
margin: 10px 0px;
}
<div id="lastname" class="toggle-div" data-hidden='true'>
<div>
<p>First bio</p>
</div>
</div>
<button class="bio-button">Click for first Bio</button>
<div id="lastname" class="toggle-div" data-hidden='true'>
<div>
<p>Second bio</p>
</div>
</div>
<button class="bio-button">Click for second Bio</button>
<div id="lastname" class="toggle-div" data-hidden='true'>
<div>
<p>Third bio</p>
</div>
</div>
<button class="bio-button">Click for third Bio</button>
<button class="bio-button-all">Show/Hide all</button>
The intension is to have a two column accordion, without limiting the "expand" field to the left or right column. The catch is that there will be multiple on one page. This is already created, but only button 1 is working. With the way my JS is going, it will get very very repetitive - I am looking for assistance with re-writing the JS to be multiple click friendly. Fiddle: https://codepen.io/ttattini/pen/abLzaaY
EDIT: It would also be perfect if one dropdown would close as the next is opened
HTML
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="row">
<div id="column">
<button id="button">I am Button #1</button>
<button id="button">I am Button #3</button>
</div>
<div id="column">
<button id="button">I am Button #2</button>
<button id="button">I am Button #4</button>
</div>
</div>
<div id="hidden">
<p id="content"> So here I am #1</p>
</div>
<div id="hidden">
<p id="content"> So here I am #2</p>
</div>
<div id="hidden">
<p id="content"> So here I am #3</p>
</div>
<div id="hidden">
<p id="content"> So here I am #4</p>
</div>
CSS
#hidden {
background: #ccc;
margin-top: 2%;
overflow: hidden;
transition: height 200ms;
height: 0; /* <-- set this */
}
#button {
padding: 10px;
margin-top: 5px;
width:50%;
margin-left: 10%;
cursor: pointer;
}
#row {
display: flex;
}
#column {
flex: 50%;
}
JS
$(function() {
var b = $("#button");
var w = $("#hidden");
var l = $("#content");
b.click(function() {
if (w.hasClass('open')) {
w.removeClass('open');
w.height(0);
} else {
w.addClass('open');
w.height(l.outerHeight(true));
}
});
});
The biggest issue is that you're using IDs when you should be using classes. IDs must be unique to each element in a page. When you repeat an ID, JS will only target the first element using that ID. That's why only the first one is working.
The second issue is that, because of the way the script is written, it will only target a single element. What you need to do is get all the elements you want to target by something like their class name and then loop through them, applying the event listener to each one and its appropriate children.
EDIT: Here is an example from some code I wrote for a page with multiple accordions a few weeks ago in vanilla JS
//Below I establish a counting variable and find all the accordions on the page
const acc = document.getElementsByClassName( 'accordion' );
let i;
//Looping through each accordion
for ( i = 1; i <= acc.length; i++ ) {
//Identify target for the event listener. In this case, a heading for each accordion, which I've numbered e.g. "title-1"
const title = 'title-' + i;
const label = document.getElementById( title );
//Identify target content, in this case a list that has a unique ID e.g. "list-1"
const listNum = 'list-' + i;
const list = document.getElementById( listNum );
//Add event listener to heading that toggles the active classes
label.addEventListener( 'click', function() {
label.classList.toggle( 'accordion--active' );
});
}
Of course, there's more than one way to skin a cat, but this is a working example.
I have tracked the clicked event of each button and showed the corresponding hidden content with the use of data- attribute.
I have used vanilla JavaScipt instead of jQuery.
const buttons = document.querySelectorAll('.button');
const hiddens = document.querySelectorAll('.hidden');
buttons.forEach((btn) => {
btn.addEventListener('click', btnClicked)
function btnClicked(e) {
hiddens.forEach((hidden) => {
if(e.target.dataset.btn == hidden.dataset.content) {
hidden.classList.toggle('height')
} else {
hidden.classList.remove('height')
}
})
}
})
.hidden {
background: #ccc;
margin-top: 2%;
padding-left:2%;
overflow: hidden;
transition: height 200ms;
height: 0; /* <-- set this */
}
.hidden.height {
height: 50px;
}
.button {
padding: 10px;
color: white;
background-color: #2da6b5;
border: none;
margin-top: 5px;
width:90%;
margin-left: 5%;
cursor: pointer;
}
.button:hover {
filter: brightness(.9);
}
#row {
display: flex;
}
.column {
flex: 50%;
}
<div id="row">
<div class="column">
<button class="button" data-btn="one">I am Button #1</button>
<button class="button" data-btn="three">I am Button #3</button>
</div>
<div class="column">
<button class="button" data-btn="two">I am Button #2</button>
<button class="button" data-btn="four">I am Button #4</button>
</div>
</div>
<div class="hidden" data-content="one">
<p class="content"> So here I am #1</p>
</div>
<div class="hidden" data-content="two">
<p class="content"> So here I am #2</p>
</div>
<div class="hidden" data-content="three">
<p class="content"> So here I am #3</p>
</div>
<div class="hidden" data-content="four">
<p class="content"> So here I am #4</p>
</div>
Also, please do not use the same ID at multiple elements.
Good day!
I have a pop-up section. There are 2 div blocks in it with identical structure. The idea is to have 2 buttons (one is to edit a profile the other is to create a new card with some info) that will call this pop-up, but i need to track which one is called. The popup itself has a darker background compare to main page and a form. I have thought of a modifier popup__container_type_(edit/create) that has a display: none command so when i toggle it it the popup would appear with the right form. Most likely my logic was mistaken. I dont know how to distiguish them (div blocks) correctly.
Another problem is that closebutton seems to work for one form only.
Any help would be great!
HTML:
<section class="popup">
<div class="popup__container popup__container_type_edit">
<button type="button" class="popup__cancelbutton"></button>
<form class="popup-form" name="form">
<h2 class="popup-form__title">Header 1</h2>
<input type="text" class="popup-form__input popup-form__input_type_name" name="name">
<input type="text" class="popup-form__input popup-form__input_type_job" name="job">
<button type="submit" class="popup-form__savebutton">Save</button>
</form>
</div>
<div class="popup__container popup__container_type_create">
<button type="button" class="popup__cancelbutton"></button>
<form class="popup-form" name="form">
<h2 class="popup-form__title">Header 2</h2>
<input type="text" class="popup-form__input popup-form__input_type_place" placeholder="Name of the place" name="place">
<input type="text" class="popup-form__input popup-form__input_type_imagelink" placeholder="Image link" name="imagelink">
<button type="submit" class="popup-form__savebutton">Create</button>
</form>
</div>
</section>
JS:
let popUpSection = document.querySelector(`.popup`);
let cancelButton = popUpSection.querySelector(`.popup__cancelbutton`);
let popUpContainer = popUpSection.querySelector(`.popup__container`);
let formElement = popUpSection.querySelector(`.popup-form`);
let newInputName = popUpSection.querySelector(`.popup-form__input_type_name`);
let newInputJob = popUpSection.querySelector(`.popup-form__input_type_job`);
let inputName = document.querySelector(`.profile-info__title`);
let inputJob = document.querySelector(`.profile-info__text`);
let editButton = document.querySelector(`.profile-info__editbutton`);
let createButton = document.querySelector(`.profile__addbutton`);
//Open / close popup section
let formTogglePopUp = () => {
if (!popUpSection.classList.contains(`popup_acitve`)){
//Autofill
newInputName.value = inputName.textContent;
newInputJob.value = inputJob.textContent;
}
popUpSection.classList.toggle(`popup_active`);
}
//Save input changes
function popUpFormSaved (event) {
event.preventDefault();
inputName.textContent = newInputName.value;
inputJob.textContent = newInputJob.value;
formTogglePopUp();
}
formElement.addEventListener('submit', popUpFormSaved);
cancelButton.addEventListener('click', formTogglePopUp);
editButton.addEventListener('click', formTogglePopUp);
createButton.addEventListener(`click`, formTogglePopUp);
CSS:
.popup__container
{
display: block; *by default*
}
.popup__container_type_(edit/create)
{
display: none;
}
.popup
{
display:none;
}
.popup__active
{
display: flex;
}
You can do it with js, set ids and use them instead of class, it's more easy.
function popUpEdit() {
document.getElementById("popUp").style.display = "block";
document.getElementById("popUpEdit").style.display = "block";
}
function popUpCreate() {
document.getElementById("popUp").style.display = "block";
document.getElementById("popUpCreate").style.display = "block";
}
#popUp, #popUpEdit, #popUpCreate {
display: none;
}
<div class="smt">
Hello
<button onclick="popUpEdit()">Edit</button>
</div>
<div class="smt">Hello
<button onclick="popUpCreate()">Create</button>
</div>
<section id="popUp">
<div>popUp</div>
<div id="popUpEdit">Edit-popup</div>
<div id="popUpCreate">Create-popup</div>
</section>
Generaly, I do that this way:
const SectionPopUp = document.querySelector('section.popup')
function show(elm)
{
SectionPopUp.classList.toggle('Create','Create'===elm)
SectionPopUp.classList.toggle('Edit','Edit'===elm)
}
section.popup,
section.popup.Edit > div:not(.popup__container_type_edit),
section.popup.Create > div:not(.popup__container_type_create) {
display:none;
}
section.popup.Edit,
section.popup.Create {
display:block;
}
/* cosmetic part, just for testing here */
section.popup > div {
border : 1px solid aqua;
padding : .6em;
margin : 1em;
width : 15em;
}
div.popup__container_type_create {
border-color: orange !important;
}
<button onclick="show('Edit')"> show Edit </button>
<button onclick="show('Create')"> show Create </button>
<button onclick="show('')"> show none </button>
<section class="popup">
<div class="popup__container popup__container_type_edit">
pop-up edit content
</div>
<div class="popup__container popup__container_type_create">
pop-up create content
</div>
</section>
Im using filtered divs and I want to change the flex-direction of each section only when that specific section is toggled, then go back to original styles when going back to "Show all"
Here is the link for the filtered divs
https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_filter_elements
The W3 Schools code is not very good. It can be improved greatly but the use of data attributes, proper event listeners and event bubbling
All we need to to to get your "flex switch" happening is add/remove a class to the ".container" to indicate if filtered or not
var container = document.querySelector(".container");
//Add an event listener to the div containing the buttons
document.getElementById("myBtnContainer").addEventListener("click", function(event) {
//remove active class from previous active button
this.querySelector(".active").classList.remove("active");
//add active class to clicked item
event.target.classList.add("active");
//Add filitered to ".container" if "All" clicked, remove otherwise
container.classList.toggle("filtered", event.target.dataset.target !== "all")
//Display chosen elements
var elements = container.querySelectorAll(".filterDiv");
for (var i = 0; i < elements.length; i++) {
//Long version of below
//var categoryArray = elements[i].dataset.category.split(",");
//var hasTargetCategory = categoryArray.includes(event.target.dataset.target);
//elements[i].classList.toggle("show",hasTargetCategory);
elements[i].classList.toggle("show", elements[i].dataset.category.split(",").includes(event.target.dataset.target));
}
})
.filterDiv {
background-color: #2196F3;
color: #ffffff;
width: 100px;
line-height: 100px;
text-align: center;
margin: 2px;
flex: none;
}
.container {
margin-top: 20px;
display: flex;
flex-wrap: wrap;
}
/* Style the buttons */
.btn {
border: none;
outline: none;
padding: 12px 16px;
background-color: #f1f1f1;
cursor: pointer;
}
.btn:hover {
background-color: #ddd;
}
.btn.active {
background-color: #666;
color: white;
}
/*Class to change flex direction*/
.filtered {
flex-direction: column;
}
/*Hide elements without the show class*/
.filtered>.filterDiv:not(.show) {
display: none;
}
<div id="myBtnContainer">
<button class="btn active" data-target="all"> Show all</button>
<button class="btn" data-target="cars"> Cars</button>
<button class="btn" data-target="animals"> Animals</button>
<button class="btn" data-target="fruits"> Fruits</button>
<button class="btn" data-target="colors"> Colors</button>
</div>
<div class="container">
<div class="filterDiv" data-category="cars">BMW</div>
<div class="filterDiv" data-category="colors,fruits">Orange</div>
<div class="filterDiv" data-category="cars">Volvo</div>
<div class="filterDiv" data-category="colors">Red</div>
<div class="filterDiv" data-category="cars,animals">Mustang</div>
<div class="filterDiv" data-category="colors">Blue</div>
<div class="filterDiv" data-category="animals">Cat</div>
<div class="filterDiv" data-category="animals">Dog</div>
<div class="filterDiv" data-category="fruits">Melon</div>
<div class="filterDiv" data-category="fruits,animals">Kiwi</div>
<div class="filterDiv" data-category="fruits">Banana</div>
<div class="filterDiv" data-category="fruits">Lemon</div>
<div class="filterDiv" data-category="animals">Cow</div>
</div>
I didn't understand what you meant by "flexible steering", would it be to replace the phrase "Show all" with the active section? This can be done with jquery, but you would have to replace the element that houses the text.
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button type="button" id="all">Show all</button>
<button type="button" id="cars">cars</button>
<script>
$("#cars").on("click", function(){
$("#all").text('Cars');
});
$("#all").on("click", function(){
$("#all").text('Show All');
});
</script>
Alternative without jquery
<button id="all">Show All</button>
<button id="cars" onclick="cars()">Cars</button>
<script>
function cars() {
document.getElementById("all").innerHTML = "Cars";
}
</script>
Alternative to build the tab without so much javascript and css
Bootstrap Collapse: You can use Bootstrap Colapse which already has a structure ready and you would only have to organize the HTML with the divs as per the documentation: https://getbootstrap.com/docs/4.0/components/collapse/
Javascript behavior: Like the previous one, this is a tabbed browsing API, which is identical to the code you are using, with the difference that here you would be using the Bootstrap structure and will not require many modifications, you should note the similarity between this and the previous one, in fact what changes is only the usability, the situations in which you will use one or the other, but the purpose is the same, both seek to hide elements.
https://getbootstrap.com/docs/4.0/components/navs/#javascript-behavior
I am making a rating bar with an element that has a data-rating attribute and it's children with 5 buttons. How can I change background-image of buttons depending on the data-rating attribute? For example if data-rating="3" - select first 3 buttons and change their background.
This is my code:
var rating = $('#video-list .children .branch-opened .rating');
rating.each(function() {
var value = $(this).data('rating');
var button = $(this).find('button');
});
HTML:
<div class="rating" data-rating="4">
<button value="1"></button>
<button value="2"></button>
<button value="3"></button>
<button value="4"></button>
<button value="5"></button>
</div>
Many thanks!
You could try the following (add to your rating.each callback function):
for (let i = 0; i < value; ++i) {
$(button[i]).css('background-color', '#abc');
}
Edit:
Has to be $(button[i]) instead of just button[i]
This simple code will work for you.
Updated Code
var rating = $('.rating');
rating.each(function() {
var rValue = $(this).data('rating');
$(this).find('button').slice(0, rValue).addClass('black');
});
.black {
background-color: black;
}
.rating button {
width: 10%;
display: inline-block;
padding: 20px;
border: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="rating" data-rating="4">
<button value="1"></button>
<button value="2"></button>
<button value="3"></button>
<button value="4"></button>
<button value="5"></button>
</div>
Use .each if you're already using jQuery:
var rating = $('.rating').data('rating');
$('.rating .item').slice(0,rating).each(function(index, value) {
$(this).toggleClass('highlighted');
});
.rating {
display: flex;
}
.item {
flex: 1;
padding: 10px;
background-color: #eee;
}
.item.highlighted {
background-color: goldenrod;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="rating" data-rating="4">
<button class="item" value="1"></button>
<button class="item" value="2"></button>
<button class="item" value="3"></button>
<button class="item" value="4"></button>
<button class="item" value="5"></button>
</div>
store the rating value and loop through the buttons and check condition if index lesser than rating value then add highlight class on the buttons.
var ratingValue = $('.rating').data('rating');
$('.rating .item').each(function (index, value) {
if ( index < ratingValue ) {
$(this).toggleClass('highlighted')
}
});
.rating {
display: flex;
flex-direction: column
}
.item {
flex: 1;
padding: 10px;
background-color: #eee;
}
.item.highlighted {
background-color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="rating" data-rating="3">
<button class="item" value="1"></button>
<button class="item" value="2"></button>
<button class="item" value="3"></button>
<button class="item" value="4"></button>
<button class="item" value="5"></button>
</div>