Function to Open and Close the Overlay using single button element - javascript

I would like to open and close overlay using single button, so when the button is clicked an additional class is added, when closed the class is removed and overlay is closed.
So far I wrote the code that opens overlay and add/remove the class to the button.
Also I've created the method to close the overlay but I'm struggling to create a proper event to actually close it, so I would be happy if anyone can guide me a bit.
I think there should be an 'if' statement within the events() checking if the button have added class, if so, the overlay will be closed using this function element.classList.contains("active");
Also the button is animated, so when class is added 3 bars (hamburger icon) becomes X and this is the main reason I don't want to have separate buttons to open and close, I already achieved that but this is not what I'm looking for.
class OverlayNav {
constructor() {
this.injectHTML()
this.hamburgerIcon = document.querySelector(".menu-icon")
this.events()
}
events() {
this.hamburgerIcon.addEventListener("click", () => this.overlayOpen())
}
overlayOpen() {
document.getElementById("myNav").style.width = "100%";
this.hamburgerIcon.classList.toggle("menu-icon--close-x")
}
overlayClose() {
document.getElementById("myNav").style.width = "0%";
}
injectHTML() {
document.body.insertAdjacentHTML('beforeend', `
<div id="myNav" class="overlay">
<p>My Overlay</p>
</div>
`)
}
}
export default OverlayNav

You can make a function with a if statement handle Opening and closing the overlay
Here is your code edited
class OverlayNav {
constructor() {
this.injectHTML();
this.hamburgerIcon = document.querySelector(".menu-icon");
this.events();
}
events() {
this.hamburgerIcon.addEventListener("click", () => this.overlayHandle());
}
overlayOpen() {
document.getElementById("myNav").style.width = "100%";
this.hamburgerIcon.classList.toggle("menu-icon--close-x");
}
overlayClose() {
document.getElementById("myNav").style.width = "0%";
}
overlayHandle() {
if (element.classList.contains("active")) {
this.overlayClose();
} else {
this.overlayOpen();
}
}
injectHTML() {
document.body.insertAdjacentHTML(
"beforeend",
`
<div id="myNav" class="overlay">
<p>My Overlay</p>
</div>
`
);
}
}
export default OverlayNav;

You can add a property that keeps track of the state of the nav bar.
constructor() {
this.injectHTML()
this.hamburgerIcon = document.querySelector(".menu-icon")
this.events()
this.overlayVisible=true;
}
Then add a method that toggles the state and calls the right open/close-method:
toggleOverlay() {
if (this.overlayVisible)
this.overlayOpen();
else
this.overlayClose();
this.overlayVisible=!this.overlayVisible;
}
Finally make the events method call toggleOverlay() instead of overlayOpen().
events() {
this.hamburgerIcon.addEventListener("click", () => this.toggleOverlay())
}

Alternativly, a pure HTML + CSS solution, using only the details element and the [open] CSS attribute selector.
.overlay > p {
padding: 1rem;
margin: 0;
display: flex;
flex-direction: column;
width: 25vw
}
.overlay summary {
padding: 1rem 0.5rem;
cursor: pointer;
max-height: 90vh;
overflow: auto;
font-size: 4em;
list-style: none;
}
.overlay[open] summary {
background: black;
color: white;
padding: 0.5rem;
font-size: 1em;
}
.overlay[open] {
position: fixed;
/* top: calc(50% - 25vw); */
left: calc(50% - 15vw);
outline: 5000px #00000090 solid;
border: 5px red solid;
border-radius: 0.5rem;
font-size: 1em
}
.overlay[open] summary::after {
content: '❌';
float: right;
}
<details class="overlay">
<summary>☰</summary>
<p>
Hello world!
</p>
</details>

Related

Is there an alternative to foreach in Js?

I have several identical divs and each of them contains a button that is hidden. I want to make button visible when you hover on the parent div. I wrote this code:
const cardElements = document.querySelectorAll('.middle_section__president_section');
const learnButtons = document.querySelectorAll('.president_section__button');
cardElements.forEach((cardElement) => {
cardElement.addEventListener('mouseover', () => {
learnButtons.forEach((learnButton) => {
learnButton.style.height = "50px";
learnButton.style.opacity = "1";
learnButton.style.border = "3px solid rgb(129, 129, 129)";
});
});
cardElement.addEventListener('mouseout', () => {
learnButtons.forEach((learnButton) => {
learnButton.style.height = "0px";
learnButton.style.opacity = "0";
learnButton.style.border = "0px solid rgb(129, 129, 129)";
});
});
})
carElements is parent, learnButtons - child.
but with this code when i hover on one div buttons appears in every similiar div. How can i make button appear only on hovered div?
Use the Event object
cardElement.addEventListener('mouseover', () => {
learnButtons.forEach((learnButton) => {
convert this to
cardElement.addEventListener('mouseover', (e) => {
var learnButton = e.target;
There's no need to use JS for this. As Mister Jojo/traktor pointed out in their comments you can use the CSS :hover pseudo-class instead.
The key CSS line is .box:hover button { visibility: visible;} which means "when you hover over the parent container make its button visible".
.box { width: 50%; display: flex; flex-direction: column; border: 1px solid lightgray; margin: 0.25em; padding: 0.25em;}
button { visibility: hidden; margin: 0.25em 0; border-radius: 5px; background-color: lightgreen; }
.box:hover button { visibility: visible;}
.box:hover, button:hover { cursor: pointer; }
<section class="box">
Some text
<button>Click for a surprise!</button>
</section>
<section class="box">
Some text
<button>Click for a surprise!</button>
</section>
<section class="box">
Some text
<button>Click for a surprise!</button>
</section>
It is bad practice to iterate over all elements and give each an event, as you can add 1 event handler to the parent and when the event happens you can check the affected element by the event parameter in the handler call back
parent.addEVentListener('mouseover', (e) => {
if(e.target.classList.contains('middle_section__president_section')) {
// Do
}
});

Is there a way to change a function by clicking on different divs?

I just started school, and this is my first question ever asked on Stackoverflow, so I apologize up front regarding both formatting and wording of this question.
I want to change the border color of my div to a style I have already declared when I click on it. To show that this has been selected.
I have three divs with id="red/green/pink".
Now, is there a way to change this function to grab information from the div I clicked, so I dont have to write 3 (almost) identical functions?
.chosenBorder{
border: 3px solid gold;
}
<div id="red" class="mainDivs" onclick="newColor('red')">Red?</div>
<div id="green" class="mainDivs" onclick="newColor('green')">Green?</div>
<div id="pink" class="mainDivs" onclick="newColor('pink')">Pink?</div>
<div class="mainDivs" onclick="whatNow(changeBig)">Choose!</div>
<script>
let changeBig = "";
let chosenDiv = document.getElementById("body");
function newColor(thisColor) {
changeBig = thisColor;
// something that make this part dynamic.classList.toggle("chosenBorder");
}
function whatNow(changeBig) {
document.body.style.backgroundColor = changeBig;
}
</script>
Since you already have an id contains the name of color; get the advantage of it: and keep track of the selected color in your variable changeBig.
let changeBig = "";
function newColor(div) {
// initial all divs to black
initialDivs();
div.style.borderColor = div.id;
changeBig = div.id;
}
function initialDivs() {
[...document.querySelectorAll('.mainDivs')].forEach(div => {
div.style.borderColor = 'black'
});
}
function whatNow() {
document.body.style.backgroundColor = changeBig;
}
.mainDivs {
padding: 10px;
margin: 10px;
border: 3px solid;
outline: 3px solid;
width: fit-content;
cursor: pointer;
}
<div id="red" class="mainDivs" onclick="newColor(this)">Red?</div>
<div id="green" class="mainDivs" onclick="newColor(this)">Green?</div>
<div id="pink" class="mainDivs" onclick="newColor(this)">Pink?</div>
<div class="mainDivs" onclick="whatNow()">Choose!</div>
There are a few (modern) modifications you can make to simplify things.
Remove the inline JS.
Use CSS to store the style information.
Use data attributes to store the colour rather than the id.
Wrap the div elements (I've called them boxes here) in a containing element. This way you can use a technique called event delegation. By attaching one listener to the container you can have that listen to events from its child elements as they "bubble up" the DOM. When an event is caught it calls a function that 1) checks that the event is from a box element 2) retrieves the color from the element's dataset, and adds it to its classList along with an active class.
// Cache the elements
const boxes = document.querySelectorAll('.box');
const container = document.querySelector('.boxes');
const button = document.querySelector('button');
// Add a listener to the container which calls
// `handleClick` when it catches an event fired from one of
// its child elements, and a listener to the button to change
// the background
container.addEventListener('click', handleClick);
button.addEventListener('click', handleBackground);
function handleClick(e) {
// Check to see if the child element that fired
// the event has a box class
if (e.target.matches('.box')) {
// Remove the color and active classes from
// all the boxes
boxes.forEach(box => box.className = 'box');
// Destructure the color from its dataset, and
// add that to the class list of the clicked box
// along with an active class
const { color } = e.target.dataset;
e.target.classList.add(color, 'active');
}
}
function handleBackground() {
// Get the active box, get its color, and then assign
// that color to the body background
const active = document.querySelector('.box.active');
const { color } = active.dataset;
document.body.style.backgroundColor = color;
}
.boxes { display: flex; flex-direction: row; background-color: white; padding: 0.4em;}
.box { display: flex; justify-content: center; align-items: center; width: 50px; height: 50px; border: 2px solid #dfdfdf; margin-right: 0.25em; }
button { margin-top: 1em; }
button:hover { cursor: pointer; }
.box:hover { cursor: pointer; }
.red { border: 2px solid red; }
.green { border: 2px solid green; }
.pink { border: 2px solid pink; }
<div class="boxes">
<div class="box" data-color="red">Red</div>
<div class="box" data-color="green">Green</div>
<div class="box" data-color="pink">Pink</div>
</div>
<button>Change background</button>

is there any better way to do this js code?

I am making these panels to be resized to fit screen height as I click 'this' element. I feel like I hard coded those javascript, and I believe there must be better way. But couldn't really sort it out.
when one panel is clicked, its size is going to get bigger, and rest of pannels gonna get smaller
I would very much appreciate any suggestion of it.
I've tried making this function reusable, but then couldn't really come up with better solution as I am a begginer.
const panels = document.querySelectorAll('.panel');
const panelsArr = Array.from(panels);
panelsArr.forEach(panel => panel.addEventListener('click',getCurrentName))
function getCurrentName(element) {
const panel1 = document.querySelector('.panel1');
const panel2 = document.querySelector('.panel2');
const panel3 = document.querySelector('.panel3');
const panel4 = document.querySelector('.panel4');
console.log(this);
if(this) {
this.classList.toggle('active');
if(this === panel1) {
panel2.classList.toggle('inactive');
panel3.classList.toggle('inactive');
panel4.classList.toggle('inactive');
} else if (this === panel2) {
panel1.classList.toggle('inactive');
panel3.classList.toggle('inactive');
panel4.classList.toggle('inactive');
} else if (this === panel3) {
panel1.classList.toggle('inactive');
panel2.classList.toggle('inactive');
panel4.classList.toggle('inactive');
} else if (this === panel4) {
panel1.classList.toggle('inactive');
panel2.classList.toggle('inactive');
panel3.classList.toggle('inactive');
}
}
}
.panel {
background-color: #002712;
box-shadow: inset 0 0 0 .5rem rgba(255,255,255,0.1);
min-height: 22.5vh;
color: #fff;
text-align: center;
font-size: 2rem;
background-size: cover;
background-position: center;
line-height: 8rem;
transition:
min-height .5s linear,
font-size .2s linear .5s,
line-height .2s linear .5s;
}
.panel1 { background-image: url("../images/steake.png"); }
.panel2 { background-image: url("../images/sundayRoast.png"); }
.panel3 { background-image: url("../images/image1(1).png"); }
.panel4 { background-image: url("../images/cannonbury.png"); }
.active {
min-height: 37vh;
line-height: 15rem;
font-size: 2.3rem;
}
.inactive {
min-height: 15vh;
font-size: 1.2rem;
}
<main>
<section class="intro">
<div class="intro-panels">
<section class="panel panel1">
<p>most original,</p>
</section>
<section class="panel panel2">
<p>best beer,</p>
</section>
<section class="panel panel3">
<p>grilled food</p>
</section>
<section class="panel panel4">
<p>Islington</p>
</section>
</div>
</section>
</main>
I expect simplified javascript code to achieve the same goal.
Here's how I've sorted it out by your answer. Thank you for your help guys.
const panels = document.querySelectorAll('.panel');
const panelsArr = Array.from(panels);
panelsArr.forEach(panel => panel.addEventListener('click', getCurrentName))
function getCurrentName(element) {
if(this) {
panelsArr.forEach(panel => panel.classList.toggle('inactive'));
this.classList.toggle('inactive');
this.classList.toggle('active');
}
}
You could mark them all as inactive and re-mark the current one as active.
Array.from(document.querySelectorAll('.panel')).forEach(element => {
element.classList.remove('active');
element.classList.add('inactive')
});
this.classList.remove('inactive');
this.classList.add('active');
You could also filter out this from the array, but it wouldn't change the outcome.
You can toggle inactive class on all class elements but undo it again on the target element:
const panels = document.querySelectorAll('.panel');
const panelsArr = Array.from(panels);
panelsArr.forEach(panel => panel.addEventListener('click',getCurrentName))
function getCurrentName(element) {
console.log(this);
if(this) {
// Toggle inactive on all elements
for (i = 0; i < panels.length; ++i) {
panels[i].classList.toggle('inactive');
}
//But undo for selected element again
this.classList.toggle('inactive');
this.classList.toggle('active');
}
}
Try something like this. Event delegation
document.addEventListener('click', (e) => {
if(e.target.matches('.sizable')) {
document.querySelectorAll('.sizable').forEach(div => {
div.classList.remove('active');
div.classList.add('inactive');
});
e.target.classList.add('active');
}
});
.sizable {
display: inline-block;
border: 1px solid black;
margin: 5px;
}
.inactive {
width: 50px;
height: 50px;
}
.active {
width: 150px;
height: 150px;
}
<div class="sizable">Stuff</div>
<div class="sizable">Stuff</div>
<div class="sizable">Stuff</div>
<div class="sizable">Stuff</div>
<div class="sizable">Stuff</div>
<div class="sizable">Stuff</div>

onclick change border colour of a html header permanently until another is clicked

Currently I have a number of clickable boxes that when I hover over them, they change colour. When I click and hold on a specific box, it changes colour.(by using :active in css.
Is there anyway I can make the colour of the border change permanently until a different box is clicked? E.G the same as the :active property except I don't have to keep the mouse held in?
My Code:
flight-viewer.html
<h3>Flights </h3>
<div>
<ul class= "grid grid-pad">
<a *ngFor="let flight of flights" class="col-1-4">
<li class ="module flight" (click)="selectFlight(flight)">
<h4>{{flight.number}}</h4>
</li>
</a>
</ul>
</div>
<div class="box" *ngIf="flightClicked">
<!--<flight-selected [flight]="selectedFlight"></flight-selected>-->
You have selected flight: {{selectedFlight.number}}<br>
From: {{selectedFlight.origin}}<br>
Leaving at: {{selectedFlight.departure || date }}<br>
Going to: {{selectedFlight.destination}}<br>
Arriving at: {{selectedFlight.arrival || date}}
</div>
flight-viewer.css:
h3 {
text-align: center;
margin-bottom: 0;
}
h4 {
position: relative;
}
ul {
width: 1600px;
overflow-x: scroll;
background: #ccc;
white-space: nowrap;
vertical-align: middle;
}
div
{
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
}
li {
display: inline-block;
/* if you need ie7 support */
*display: inline;
zoom: 1
}
.module {
padding: 20px;
text-align: center;
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #607D8B;
border-radius: 2px;
}
.module:hover {
background-color: #EEE;
cursor: pointer;
color: #607d8b;
}
.module:active {
border: 5px solid #73AD21;
}
.box {
text-align: center;
margin-bottom: 0;
margin: auto;
width: 600px;
position:absolute;
top: 180px;
right: 0;
height: 100px;
border: 5px solid #73AD21;
text-align: center;
display: inline-block;
}
flight-viewer-component.ts:
import { Component } from '#angular/core';
import { FlightService } from './flight.service';
import { Flight } from './flight.model'
#Component({
selector: 'flight-viewer',
templateUrl: 'app/flight-viewer.html',
styleUrls: ['app/flight-viewer.css']
})
export class FlightViewerComponent {
name = 'FlightViewerComponent';
errorMessage = "";
stateValid = true;
flights: Flight[];
selectedFlight: Flight;
flightToUpdate: Flight;
flightClicked = false;
constructor(private service: FlightService) {
this.selectedFlight = null;
this.flightToUpdate = null;
this.fetchFlights();
}
flightSelected(selected: Flight) {
console.log("Setting selected flight to: ", selected.number);
this.selectedFlight = selected;
}
flightUpdating(selected: Flight) {
console.log("Setting updateable flight to: ", selected.number);
this.flightToUpdate = selected;
}
updateFlight(flight: Flight) {
let errorMsg = `Could not update flight ${flight.number}`;
this.service
.updateFlight(flight)
.subscribe(() => this.fetchFlights(),
() => this.raiseError(errorMsg));
}
selectFlight(selected: Flight) {
console.log("Just click on this flight ", selected.number, " for display");
this.flightClicked = true;
this.selectedFlight = selected;
}
private fetchFlights() {
this.selectedFlight = null;
this.flightToUpdate = null;
this.service
.fetchFlights()
.subscribe(flights => this.flights = flights,
() => this.raiseError("No flights found!"));
}
private raiseError(text: string): void {
this.stateValid = false;
this.errorMessage = text;
}
}
Thanks!
I'm quite sure that this has already been answered.
Make your DIVs focusable, by adding tabIndex:
<div tabindex="1">
Section 1
</div>
<div tabindex="2">
Section 2
</div>
<div tabindex="3">
Section 3
</div>
Then you can simple use :focus pseudo-class
div:focus {
background-color:red;
}
Demo: http://jsfiddle.net/mwbbcyja/
You can use the [ngClass] directive provided by angular to solve your problem.
<a *ngFor="let flight of flights" class="col-1-4">
<li [ngClass]="{'permanent-border': flight.id === selectedFlight?.id}" class ="module flight" (click)="selectFlight(flight)">
<h4>{{flight.number}}</h4>
</li>
</a>
This will add the css class permantent-border to the <li> element, if the id of the flight matches the id with the selectedFlight (Assuming you have an id proberty specified, or just use another proberty which is unique for the flight)

How to toggle a selected class in a global click event?

I'm creating my component library in vue, and I defined my component checkbox, the code is like this:
<template>
<div class="checkboxcont" :class="{'checkboxcont-selected': isSelected}" #click="clickevent">
<span class="j-checkbox">
<input type="checkbox" />
</span>
<slot></slot>
</div>
</template>
<script>
export default {
data() {
return {
isSelected: false
}
},
methods: {
clickevent(event) {
if(this.isSelected) {
this.isSelected = false;
} else {
this.isSelected = true;
}
}
},
}
</script>
Now, I hope that when I click the checkbox to set the data "isSelected" false, I can give the component class "checkboxcont-selected-last", and when I click other checkbox component, the classname "checkboxcont-selected-last" can be removed, how can I listen my click event to finish it? I try to use native JavaScript code to add the classname of the dom, but it seemed to have nothing when I binded the classname of my component with Vue.js:
clickevent(event) {
if(this.isSelected) {
this.isSelected = false;
this.$el.classList.add("checkboxcont-selected-last");
} else {
this.isSelected = true;
}
}
What should I do to solve this problem, please?
Here is my style code using less:
<style lang="less" scoped rel="stylesheet/less">
#import '../../mixin/mixin.less';
.checkboxcont {
display: inline-block;
&:hover {
cursor: pointer;
.j-checkbox {
border-color: #jbluelight;
}
}
}
.j-checkbox {
position: relative;
top: 0;
left: 0;
width: 12px;
height: 12px;
display: inline-block;
border: 1px solid #border;
border-radius: 3px;
line-height: 12px;
vertical-align: -3px;
margin: 0 5px;
z-index: 20;
transition: all .2s linear;
input {
opacity: 0;
position: absolute;
left: 0;
top: 0;
visibility: hidden;
/*display: none;*/
}
}
.checkboxcont-selected {
.j-checkbox {
background: #jbluelight;
border-color: #jbluelight;
&:after {
content: '';
width: 4px;
height: 7px;
border: 2px solid white;
border-top: none;
border-left: none;
position: absolute;
left: 3px;
top: 0;
z-index: 30;
transform: rotate(45deg) scale(1);
}
}
}
</style>
<style lang="less" rel="stylesheet/less">
#import '../../mixin/mixin.less';
.checkboxcont-selected-last .j-checkbox {
border-color: #jbluelight;
}
</style>
My initial thought is that I add the class by using this.$el after I clicked the component, it can be accessed because I dispatched the click event, and I just can't access the other component:
if(this.isSelected) {
this.isSelected = false;
this.$el.classList.add("checkboxcont-selected-last")
} else {
this.isSelected = true;
}
And I remove the class by using native HTML DOM operation when I dispatch the click event because I can not access the other component, so the complete definition of clickevent is that:
clickevent(event) {
let selectedLast = document.querySelector(".checkboxcont-selected-last");
if(selectedLast) {
selectedLast.classList.remove("checkboxcont-selected-last")
}
if(this.isSelected) {
this.isSelected = false;
this.$el.classList.add("checkboxcont-selected-last")
} else {
this.isSelected = true;
}
}
It looks good, but I can not add classname of my component when I use v-bind to bind my component's classname, is it wrong? And Is it unable to use native HTML DOM operation when I bind my component's classname with Vue?
A better way to dynamically add or remove class can be using v-bind:class. There are different ways you can add a dynamic class based on a vue data variable.
I see you are already using it:
<div class="checkboxcont" :class="{'checkboxcont-selected': isSelected}" #click="clickevent">
So here this div will have only one class : checkboxcont if isSelected is false, and two classes : checkboxcont and checkboxcont-selected if isSelected is true.
Edited:
Given that you want to add a class to DOM on another component, I can think of two ways:
Using Web API: You can do following if you know the id of the element you want to add class using Element.className:
var d = document.getElementById("yourElem") d.className += " otherclass"
Vuex way: You can have a centralised state provided by vue or use vuex to manage state, these state variables can be changed across components, and can be used to add/remove class dynamically.
You can have a look at my this answer to understand more about vuex.

Categories

Resources