Recognizing when sidebar is active or not - javascript

I want to use javaScript to recognize when a sidebar is classed "active" or not. I'm using bootstraps' sidebar toggle button, which when clicked, assigns a class of "active" to the sidebar.
<button type="button" id="sidebarCollapse" class="btn btn-info" style="font-family:'Poppins'; position:absolute; z-index:9; margin-left:7vh; margin-top:2vh;font-size: 1.5em">
<span class="glyphicon glyphicon-filter"></span> Filter
</button>
The CSS:
#sidebar {
background: #202020;
color: #fff;
display:inline-block;
}
#sidebar.active {
margin-left: -250px;
}
And, the JS:
//Check to see whether sidebar has class 'active'
var sideBar = document.getElementById('sidebar')
console.log(sideBar.className)
if (sideBar.className == ('active')){
console.log('active')
}
else (console.log('not active'))
To be clear, the active class is only assigned when the sidebarCollapse button is clicked, and the active class is removed when the button is clicked again. The above code doesn't work. It only logs 'not active', even when the sidebar is clearly classed 'active' and is visible. I want it to dynamically read the status of the sidebar (either classed active, or not active).
var sideBar = document.getElementById('sidebar');
console.log(sideBar.className)
if (sideBar.classList.contains('active')){
console.log('active')
}
else (console.log('not active'))
Here's pictures of the HTML, showing the two states of the sidebar (active/not active):

You code should work. There are 2 reasons why your code is always showing 'not active'
Your code is executed on page load
You are fetching the sidebar div before sidebar got opened and the dom object is not updated later.
Move your code to a function and call that function when ever you need to check.
Sample code below.
function isSidebarOpen() {
var sideBar = document.getElementById('sidebar');
//console.log(sideBar.classList)
if (sideBar.classList.contains('active')) {
console.log('active')
} else(console.log('not active'))
}
<div id="sidebar" class="active">
test
<button onclick='isSidebarOpen()'>
Check</button>
</div>

Use MutationObserver.
Add the code below to observe the change:
const targetNode = document.getElementById('sidebarCollapse'); //listen to the sidebar
const config = { attributes: true }; //listen for changes in attributes
const callback = function(mutationsList, observer) {
for(let mutation of mutationsList) {
if (mutation.type === 'attributes') {
if (targetNode.classList.contains('active')){
console.log('active');
}
}
}
};
const observer = new MutationObserver(callback); //construct observer
observer.observe(targetNode, config); //start observing
A working pen here.

Related

How to make an event toggle HTML class on one element while removing it from others?

So, I have a little project that showcases a set of images, small-size. And when you click on one of them, it expands. Basically, JavaScript adds an HTML class of "active" to an element, which transforms it.
const panels = document.querySelectorAll('.panel');
/*adding an event for every image panel that makes
panel active on click */
panels.forEach(panel => {
panel.addEventListener('click', () => {
removeActiveClasses()
panel.classList.add('active')
})
})
//a function that removes active class from a panel
function removeActiveClasses() {
panels.forEach(panel => {
panel.classList.remove('active')
})
}
So, when you click on the element, it removes all the .active classes from every other one, and then adds it to a target element. It works perfectly, but I want to be able to delete a class from an element that is currently active, so when I click on an expanded picture, it collapses back. Changing panel.classList.add to panel.classList.toggle obviously doesn't work, because it first removes all active classes and then "toggles" it (or adds, because there is none). How can I delete a class from active element on click, while remaining the other functionality?
Solution: Thanks to CBroe I've managed to make it work. The code now looks like the following:
const panels = document.querySelectorAll('.panel');
/*adding an event for every image panel that makes
panel active on click */
panels.forEach(panel => {
panel.addEventListener('click', () => {
const isActive = panel.classList.contains('active')
removeActiveClasses()
panel.classList.toggle('active', !isActive)
})
})
//a function that removes active class from a panel
function removeActiveClasses() {
panels.forEach(panel => {
panel.classList.remove('active')
})
}
A much more efficient way would be to :
document.getElementsByClassName('panel').addEventListener('click', function() {
for (let ele of panels) {
ele.classList.remove('active')
}
let panel = this
panel.classList.includes('active') ? panel.classList.remove('active') : panel.classList.add('active')
)

Can't get overlay to show up in javascript, but could in jquery

I want an overlay to show up when I click a search icon.
I managed to get it working using jQuery. But can't seem to get it working with javascript.
The click event does not seem to be registering and I don't know why.
I've checked all the class names so they match in the same in both the HTML and javascript
Here is the jQuery code that works:
import $ from 'jquery';
class Search {
constructor() {
this.openButton = $('.js-search-trigger');
this.closeButton = $('.search-overlay__close');
this.searchOverlay = $(".search-overlay");
this.events();
}
events() {
this.openButton.on('click', this.openOverlay.bind(this));
this.closeButton.on('click', this.closeOverlay.bind(this));
}
openOverlay() {
this.searchOverlay.addClass("search-overlay--active");
}
closeOverlay() {
this.searchOverlay.removeClass("search-overlay--active");
}
}
export default Search;
Here is the javascript code that does not work:
class Search {
constructor() {
this.openButton = document.querySelector('.js-search-trigger');
this.closeButton = document.querySelector('.search-overlay__close');
this.searchOverlay = document.querySelector('.search-overlay');
this.events();
}
events() {
this.openButton.addEventListener('click', function() {
this.openOverlay.bind(this);
});
this.closeButton.addEventListener('click', function() {
this.closeOverlay.bind(this);
});
}
openOverlay() {
this.searchOverlay.classList.add('search-overlay--active');
}
closeOverlay() {
this.searchOverlay.classList.remove('search-overlay--active');
}
}
export default Search;
No errors were shown in the javascript where the overlay was not showing.
You'll probably want to change your event listeners to use the correct this binding:
this.openButton.addEventListener("click", this.openOverlay.bind(this));
Or use an arrow function to go with your approach - but make sure you actually call the resulting function, as in the above approach the function is passed as a reference and is called. If you removed the additional () from the code below, it would be the same as writing a function out in your code normally - it would be defined, but nothing would happen.
this.openButton.addEventListener("click", () => {
this.openOverlay.bind(this)();
});
jQuery also uses collections of elements rather than single elements, so if you have multiple elements, querySelectorAll and forEach might be in order.
If we are speaking of ecmascript-6 (I see the tag), I would recommend to use arrow function to have this inherited from the above scope, and no bind is needed:
this.openButton.addEventListener('click', () =>
this.openOverlay()
);
The problems with your code are that a) the function creates new scope with its own this; b) bound methods are not being invoked.
Why Search? You're creating an Overlay. Stick with the plan.
No need to bind anything. Use Event.currentTarget if you want to.
No need to handle .open/.close if all you need is a toggle.
And the below should work (as-is) for multiple Overlays. The overlay content is up to you.
class Overlay {
constructor() {
this.toggleButtons = document.querySelectorAll('[data-overlay]');
if (this.toggleButtons.length) this.events();
}
events() {
this.toggleButtons.forEach(el => el.addEventListener('click', this.toggleOverlay));
}
toggleOverlay(ev) {
const btn = ev.currentTarget;
const sel = btn.getAttribute('data-overlay');
const overlay = sel ? document.querySelector(sel) : btn.closest('.overlay');
overlay.classList.toggle('is-active');
}
}
new Overlay();
*{margin:0; box-sizing:border-box;} html,body {height:100%; font:14px/1.4 sans-serif;}
.overlay {
background: rgba(0,0,0,0.8);
color: #fff;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
padding: 5vw;
transition: opacity 0.4s, visibility 0.4s;
visibility: hidden;
opacity: 0;
}
.overlay.is-active {
opacity: 1;
visibility: visible;
}
<button type="button" data-overlay="#search">OPEN #search</button>
<button type="button" data-overlay="#qa">OPEN #qa</button>
<div class="overlay" id="search">
<button type="button" data-overlay>CLOSE</button>
<h2>SEARCH</h2>
<input type="text" placeholder="Search…">
</div>
<div class="overlay" id="qa">
<button type="button" data-overlay>CLOSE</button>
<h2>Q&A</h2>
<ul><li>Lorem ipsum</li></ul>
</div>
The above is still not perfect, still misses a way to "destroy" events and not re-attach duplicate events to already initialised buttons when trying to target dynamically created ones.
Also, the use of Classes for the above task is absolutely misleading and unnecessary.

Combine Mobile and Screen function

Trying to get this function to work properly. The toggleNav works on it's own. I want to apply different Open/close functionality based on screen size. the navigation opens and closes based on mouse events, so the code needs to continuously run at each screen size.
let opened = false; // set the nav as closed by default
if ($(window).width() > 720) {
function toggleNav() {
if(!opened) { // if opened is false (ie nav is closed), open the nav
openNav()
} else { // else, if opened is ture (ie nav is open), close the nav
closeNav();
}
opened = !opened; // negate boolean to get opposite (t to f, and f to t)
}
}else{
function toggleNav2() {
if(!opened) {
openNav2()
} else {
closeNav2();
}
opened = !opened;
}
}
function openNav() {
$('#myTopnav').addClass('openHeight').removeClass('closeHeight');
$('#main').addClass('openMain').removeClass('closeMain');
}
function closeNav() {
$('#myTopnav').removeClass('openHeight').addClass('closeHeight');
$('#main').removeClass('openMain').addClass('closeMain');
}
function openNav2() {
$('#main').addClass('openMain').removeClass('closeMain');
}
function closeNav2() {
$('#main').removeClass('openMain').addClass('closeMain');
}
I'm guessing that you're asking how to make this work in the event that the user resizes their view.
I would recommend rearranging your styles so that there is one master class that controls this feature, on a single parent element (e.g.: body), which you can then toggle on and off via javascript. In this way, all style changes (including those caused by user view size changes) are handled by CSS and javascript only has to manage a single element.
You don't provide enough of your code to rework what you're doing but, as an example (click "expand snippet" and resize your browser window as needed to experiment with the width):
$('button').on('click', e => {
$('body').toggleClass('off');
});
.top:after {
display:block;
content:"on";
color: #080;
}
.bottom:after {
display:block;
content:"on";
color: #080;
}
/* overrides for "off" state */
.off .top:after {
content:"off";
color: #800;
}
/* only override css if view width > 720px */
#media (min-width:721px) {
.off .bottom:after {
content:"off";
color: #800;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button type="button">toggle</button>
<div class="top"></div>
<div class="bottom"></div>

Click event on div with img and text not firing when clicking img

I wrote toggle script in ES6/vanilla JS. The intended functionality is super simple, you click on the toggle div and it adds an active class to another div that matches the toggle div's data-toggle property. In my toggle div, I need there to be both text and an image. It works great when you click on the text within the div, but when you click on the image within the div, the toggle is not firing. Is there something specific I need to do to include all of the children within the div?
For some reason, I can't even get this working via this code snippet editor, but it is working in my project.
const setActive = (toggles, panels, id) => {
let activePanel = panels.filter(panel => panel.getAttribute('data-toggle') == id)
let activeToggle = toggles.filter(toggle => toggle.getAttribute('data-toggle') == id)
activePanel.forEach(panel => panel.classList.add('active'))
activeToggle.forEach(toggle => toggle.classList.add('active'))
}
const removeActive = (nodes) => {
nodes.forEach(node => node.classList.remove('active'))
}
const handler = (event) => {
event.preventDefault()
let id = event.target.getAttribute('data-toggle')
let panels = Array(...document.querySelectorAll('.js-toggle-panel'))
let toggles = Array(...document.querySelectorAll('.js-toggle'))
removeActive(panels)
removeActive(toggles)
setActive(toggles, panels, id)
}
let toggles = Array(...document.querySelectorAll('.js-toggle'))
toggles.forEach(toggle => toggle.addEventListener('click', handler))
.toggle-panel {
display: none;
}
.toggle-panel .active {
display: block;
}
<div class="js-toggle toggle" data-toggle="toggle-1">
<img src="https://via.placeholder.com/50"> First toggle
</div>
<div class="js-toggle toggle" data-toggle="toggle-2">
Second toggle
</div>
<div class="js-toggle-panel toggle-panel" data-toggle="toggle-1">
<h1>Toggle 1</h1>
</div>
<div class="js-toggle-panel toggle-panel" data-toggle="toggle-2">
<h1>Second toggle!</h1>
</div>
I changed two things that I believe will resolve your issue:
I changed the selector .toggle-panel .active to .toggle-panel.active-- without that, even in the cases where the JS was working as you intended nothing was actually be made visible.
I moved your code from using event.target to event.currentTarget -- the former always points to the clicked element, whereas the latter refers to the element on which the listener has been placed.
See the snippet below.
const setActive = (toggles, panels, id) => {
let activePanel = panels.filter(panel => panel.getAttribute('data-toggle') == id)
let activeToggle = toggles.filter(toggle => toggle.getAttribute('data-toggle') == id)
activePanel.forEach(panel => panel.classList.add('active'))
activeToggle.forEach(toggle => toggle.classList.add('active'))
}
const removeActive = (nodes) => {
nodes.forEach(node => node.classList.remove('active'))
}
const handler = (event) => {
event.preventDefault()
let id = event.currentTarget.getAttribute('data-toggle')
let panels = Array(...document.querySelectorAll('.js-toggle-panel'))
let toggles = Array(...document.querySelectorAll('.js-toggle'))
removeActive(panels)
removeActive(toggles)
setActive(toggles, panels, id)
}
let toggles = Array(...document.querySelectorAll('.js-toggle'))
toggles.forEach(toggle => toggle.addEventListener('click', handler))
.toggle-panel {
display: none;
}
.toggle-panel.active {
display: block;
}
<div class="js-toggle toggle" data-toggle="toggle-1">
<img src="https://via.placeholder.com/50"> First toggle
</div>
<div class="js-toggle toggle" data-toggle="toggle-2">
Second toggle
</div>
<div class="js-toggle-panel toggle-panel" data-toggle="toggle-1">
<h1>Toggle 1</h1>
</div>
<div class="js-toggle-panel toggle-panel" data-toggle="toggle-2">
<h1>Second toggle!</h1>
</div>
Instead of event.target you should use event.currentTarget in your handler function to return node to which event listener is attached. event.target is returning <img> node, not <div> with data-toggle in your case.

MutationObserver on nodelist

I have some html with four section:
<section class="section"><div class="button"></div></div>
<section class="section"><div class="button"></div></div>
<section class="section"><div class="button"></div></div>
<section class="section"><div class="button"></div></div>
Inside of this section there is a div.button class, when the user clicks on it, the section gets expanded, covering the entire page (all the element inside become visible), and receives the class 'is-expanded'
Now i set a MutationObserver on those nodes (with class 'section'):
utilities.js
class Observer{
constructor(targetClass,buttonHandler){
this.targetClass = targetClass;
}
initObserver(){
let self = this;
const observer = new MutationObserver(function(mutations){
mutations.forEach(function(mutation){
if(mutation.target.classList.contains(self.targetClass)){
let menuButton = Array.from(mutation.target.children).find(function(el){
return el.classList.contains('menu-heading')
})
self.buttonHandler.disable(menuButton)
console.log(menuButton)
}
else if(!mutation.target.classList.contains(self.targetClass)){
let menuButton = Array.from(mutation.target.children).find(function(el){
return el.classList.contains('menu-heading')
})
self.buttonHandler.enable(menuButton)
}
})
}
return observer
}
}
main.js
const observer = new utilities.Observer(expandedClass).initObserver()
const sections = Array.from(document.querySelectorAll('.section'));
document.addEventListener('DOMContentLoaded',function(){
sections.forEach(function(section){
observer.observe(section,{attributes:true})
})
})
I'm new to mutation observe api, could be this a good use of it? even in terms of performance (instead of use addEventListener on click etc)
thanks
EDIT
I update the code, for now what i want is that if section has the class expanded, the children button (with class .menu-heading) get the css style pointerEvents set to none, to auto if not has the target class. the enable function from buttonhandler class do the job

Categories