querySelectorAll and multiple elements with the same class - javascript

I am currently trying to adapt this demo for page transitions when you click on the links with the same class applied to them.
I am not sure how to adapt the following piece of code for all the elements that has the same class following the use of querySelectorAll. What do you think should be tweaked to make it work with querySelectorAll and multiple elements with the same class?
(function() {
const elmHamburger = document.querySelector('.link-with-overlay');
const elmOverlay = document.querySelector('.shape-overlays');
const overlay = new ShapeOverlays(elmOverlay);
elmHamburger.addEventListener('click', () => {
if (overlay.isAnimating) {
return false;
}
overlay.toggle();
if (overlay.isOpened === true) {
elmHamburger.classList.add('is-opened-navi');
} else {
elmHamburger.classList.remove('is-opened-navi');
}
Thanks!

To allow all the links to have an onclick event you'll need to iterate over the NodeList that the querySelectorAll method returns.
NOTE: You cannot do NodeList.forEach in IE11 so you'd need to polyfill or convert it to a true JS array before iterating.
(function() {
const elmHamburgers = document.querySelectorAll('.link-with-overlay');
const elmOverlay = document.querySelector('.shape-overlays');
const overlay = new ShapeOverlays(elmOverlay);
const onHamburgerClick = function() {
if (overlay.isAnimating) {
return false;
}
overlay.toggle();
if (overlay.isOpened === true) {
this.classList.add('is-opened-navi');
} else {
this.classList.remove('is-opened-navi');
}
};
// Iterates over all of the elements matched with class .link-with-overlay and
// adds an onclick event listener
elmHamburgers.forEach(elem => elem.addEventListener('click', onHamburgerClick));
})();
You could also replace the conditional: if (overlay.isOpened === true) {...
with a one liner using this.classList
this.classList.toggle('is-opened-navi', overlay.isOpened)

Related

How to return a wrapper Object instead of HTML element?

I am writing a micro-library instead of using jQuery. I need only 3-4 methods ( for DOM traversal, Adding Eventlisteners etc). So I decided to write them myself instead of bloating the site with jQuery.
Here is the snippet from the code:
lib.js
window.twentyFourJS = (function() {
let elements;
const Constructor = function(selector) {
elements = document.querySelectorAll(selector);
this.elements = elements;
};
Constructor.prototype.addClass = function(className) {
elements.forEach( item => item.classList.add(className));
return this;
};
Constructor.prototype.on = function(event, callback, useCapture = false){
elements.forEach((element) => {
element.addEventListener(event, callback, useCapture);
});
return this;
}
const initFunction = function(selector){
return new Constructor(selector);
}
return initFunction;
})(twentyFourJS);
script.js
(function($){
$('.tab-menu li a').on('click', function(event) {
console.log('I am clicked'); // This works
this.addClass('MyClass'); // This does NOT work (as expected)
// I want to be able to do this
$(this).addClass('MyClass');
event.preventDefault();
});
})(twentyFourJS);
Basically I want to be able to use $(this) like we use it in jQuery.
this.addClass('MyClass') and $(this).addClass('MyClass') won't work and this is the expected behaviour.
As per my understanding this is referring to the plain HTML element. So it does not have access to any Constructor methods. it won't work.
And I have not written any code that will wrap element in Constructor object in $(this). I will have to do some changes to my Constructor so that I can access the Constructor functions using $(this). What are those changes/addition to it?
Kindly recommend only Vanilla JS ways instead of libraries.
in your constructor you need to see what you have and handle it in different ways.
const Constructor = function(selector) {
if (typeof selector === 'string') {
elements = document.querySelectorAll(selector);
} else {
// need some sort of check to see if collection or single element
// This could be improved since it could fail when someone would add a length property/attribute
elements = selector.length ? selector : [selector];
}
this.elements = elements;
};
All you really need to do is make sure your Constructor argument can distinguish between a string selector being passed in, and an object.
const Constructor = function(selector) {
if(typeof selector == "string"){
elements = document.querySelectorAll(selector);
this.elements = elements;
}
else{
this.elements = selector;
}
};
You can go further than this, but at a very minimum for the example given that works.
Live example below:
window.twentyFourJS = (function() {
let elements;
const Constructor = function(selector) {
if(typeof selector == "string"){
elements = document.querySelectorAll(selector);
this.elements = elements;
}
else{
this.elements = selector;
}
};
Constructor.prototype.addClass = function(className) {
elements.forEach( item => item.classList.add(className));
return this;
};
Constructor.prototype.on = function(event, callback, useCapture = false){
elements.forEach((element) => {
element.addEventListener(event, callback, useCapture);
});
return this;
}
const initFunction = function(selector){
return new Constructor(selector);
}
return initFunction;
})();
(function($){
$('.btn').on('click', function(event) {
console.log('I am clicked'); // This works
// I want to be able to do this
$(this).addClass('MyClass');
event.preventDefault();
});
})(twentyFourJS);
.MyClass{
background-color:red
}
<button class="btn">Click me</btn>
first You Need to Check for a string
case 1. $("div")
Then You need to Check for it's NodeType and for a window
case 1. var elm = document .getElementById("ID")
$(elm)
case 2. $(this) -- window
function $(selector){
var element;
if (typeof selector === 'string') {
element = document.querySelectorAll(selector)
}
if (element.nodeType || element=== window) element= [selector];
return element ;
}

Adding a class to a created element

The parameter of my function is a function. It should create an element but I should still be able to add attributes by using the parameter details.
E.g.:
const addElement = (details) => {
const element = document.createElement('div');
}
addElement(function() {
element.id = 'my-div'; // Not working since element is not defined
});
Well, I have tried to store the element in an object to be able to use it outside of that function.
let element = {};
const displayVideo = (type, details) => {
element = document.createElement(type);
element.width = 200;
element.height = 200;
element.classList.add('my-class'); // <--- THE PROBLEM!
if (details) {
details();
}
document.querySelector('#layer').insertBefore(element, document.querySelector('#el'));
};
displayVideo('VIDEO', function () {
element.controls = true;
});
My element can not be created because of element.classList.add('my-class'); and I don't even get an error message. If I remove that line, it works but I would still like to be able to add a class to that object. How can I do this?
Just pass element into the function. Since you're just editing properties on the object, this won't cause reference vs value errors.
const addElement = (details) => {
const element = document.createElement('div');
if (details) details(element);
return element;
}
const ele = addElement(function(element) {
element.id = 'my-div';
});
console.log(ele);
In this case details could be something like classname.
function element(type, classname) {
var element = document.createElement(type);
if (classname !== undefined) {
element.classList.add(classname);
}
return element;
};
element("div","my-class"); //<div class="my-class"></div>
Of course instead of classname you could use an array or an object and loop through in order to set multiple attributes.
Or you could store the return value of your function in a variable and then add all the attributes:
var myelement = element("div");
myelement.classList.add("my-new-class");
myelement //<div class=​"my-new-class">​</div>​

JS HTML CSS: how to make a function dynamically repeat with different variables each time while not repeating the same variables inside de function

Intertwining Functions
I've been trying to make functions different but both have have the same original function.
Here are my selectors:
const popControl = document.getElementById("pop");
const popHeader = document.getElementById("header");
const popButton = document.getElementById("close");
const popTitle = document.getElementById("title");
const popBody = document.getElementById("body");
const popControl2 = document.getElementById("pop2");
const popHeader2 = document.getElementById("header2");
const popButton2 = document.getElementById("close2");
const popTitle2 = document.getElementById("title2");
const popBody2 = document.getElementById("body2");`
With all my id's selected, I create my first function, called verifyPosition:
function verifyPosition(circleLeftPosition, circleRightPosition, moveBy) {
console.log(circleLeftPosition, circleRightPosition);
if (circleLeftPosition == "550px" && circleRightPosition == "75px") {
popButton.addEventListener("click", closePosition);
openPosition();
}
}
Now, I must create the other, malfunctioning func, verifyPosition2:
function verifyPosition2(circleLeftPosition, circleRightPosition, moveBy) {
console.log(circleLeftPosition, circleRightPosition);
if (circleLeftPosition == "550px" && circleRightPosition == "75px") {
popButton2.addEventListener("click", closePosition2);
openPosition2();
}
}
For some reason, my verifyPosition2 does not work, and it does not matter what you put in it.
This brings me to my final questions:
Why is it not working?
And how can I make it work?
Thanks and thanks all!
Hint: [!ch.edit] tag means I have edited this question.
You may try the below approach
function verifyPosition(circleLeftPosition, circleRightPosition, moveBy, selector_extension) {
if (circleLeftPosition == "550px" && circleRightPosition == "75px") {
const popButton = document.getElementById("close" + selector_extension);
// Checkout the below code, if verifyPosition is called multiple times on same element, there is chance of setting multiple click events on popButton
popButton.addEventListener("click", function (e) { return closePosition(e, selector_extension); });
openPosition(selector_extension);
}
}
function closePosition(e, selector_extension) {
// access the required elements using `selector_extension`
}
function openPosition(selector_extension) {
// access the required elements using `selector_extension`
}

Remove event listener doesn't work as it should

I simply tried to addEventListener and removeEventListener to element, but it doesn't remove.
I suppose that the problem could be with parameters, but I used them to follow the DRY. So I could simply reuse it like nextSection.addEventListener('mouseover', showContent(event, nextSection)) and so on and so on so I do not need any if statements or stuff like that.
* EDIT *
I made some more examples of elements that I will be using. There’s a chance, that there will be event more. If I do not use parameter, there would be a lot more of functions. Also, there will be click instead of mouse events on mobile, so I need to remove them.
As I understand now, the problem is with return statement. If I use event instead of parameter and so event.target I get some weird bug.
const loginSection = document.querySelector('#js-login-section');
const searchSection = document.querySelector('#js-search-section');
const shoppingBagSection = document.querySelector('#js-shopping-bag-section');
const wishlistSection = document.querySelector('#js-wishlist-section');
function showContent(element) {
return () => {
const toggle = element.lastElementChild;
toggle.style.maxHeight = toggle.scrollHeight + 'px';
}
}
function hideContent(element) {
return () => {
const toggle = element.lastElementChild;
toggle.style.maxHeight = null;
}
}
/* Media queries - min width 992px */
loginSection.addEventListener('mouseover', showContent(loginSection));
loginSection.addEventListener('mouseout', hideContent(loginSection));
searchSection.addEventListener('mouseover', showContent(searchSection));
searchSection.addEventListener('mouseout', hideContent(searchSection));
shoppingBagSection.addEventListener('mouseover', showContent(shoppingBagSection));
shoppingBagSection.addEventListener('mouseout', hideContent(shoppingBagSection));
wishlistSection.addEventListener('mouseover', showContent(wishlistSection));
wishlistSection.addEventListener('mouseout', hideContent(wishlistSection));
/* Media queries - max width 992px */
loginSection.removeEventListener('mouseover', showContent(loginSection));
loginSection.removeEventListener('mouseout', hideContent(loginSection));
searchSection.removeEventListener('mouseover', showContent(searchSection));
searchSection.removeEventListener('mouseout', hideContent(searchSection));
shoppingBagSection.removeEventListener('mouseover', showContent(shoppingBagSection));
shoppingBagSection.removeEventListener('mouseout', hideContent(shoppingBagSection));
wishlistSection.removeEventListener('mouseover', showContent(wishlistSection));
wishlistSection.removeEventListener('mouseout', hideContent(wishlistSection));
Thank you in advance!
What is happening is that return () => {}; is returning a new function every time it's run. So every time you call one of your functions a new event handler is being created.
This means that the handler that is added is different to the one you're trying to remove.
To remedy this, I'd keep it simple:
const loginSection = document.querySelector('#js-login-section');
function showContent(e)
{
const toggle = e.currentTarget.lastElementChild;
toggle.style.maxHeight = toggle.scrollHeight + 'px';
}
function hideContent(e)
{
const toggle = e.currentTarget.lastElementChild;
toggle.style.maxHeight = null;
}
loginSection.addEventListener('mouseover', showContent);
loginSection.addEventListener('mouseout', hideContent);
loginSection.removeEventListener('mouseover', showContent);
loginSection.removeEventListener('mouseout', hideContent);
I'm not sure what you want to avoid repeating, so I can't advise on that, but I'm sure you'll figure it out.
const loginSection = document.querySelector('#js-login-section');
function showContent(event) {
var element = event.target;
return () => {
const toggle = element.lastElementChild;
toggle.style.maxHeight = toggle.scrollHeight + 'px';
}
}
function hideContent(event) {
var element = event.target;
return () => {
const toggle = element.lastElementChild;
toggle.style.maxHeight = null;
}
}
loginSection.addEventListener('mouseover', showContent);
loginSection.addEventListener('mouseout', hideContent);
loginSection.removeEventListener('mouseover', showContent);
loginSection.removeEventListener('mouseout', hideContent);
You must set in events method function without call. Element you can get from event event.target
In your code, I found the following errors,
param 'event' will be always undefined - the event should go as a parameter to inner function.
you don't need closure here - You can directly assign the function without creating an inner function and access the element with event.target or this
with your implementation, you should pass the same handler reference used in addEventListener to removeEventListener. So, you should store the handler in a variable and pass it to both addEventListener and removeEventListener
Solution:
if you don't know the handler name, you can use window.getEventListeners to do the magic,
window.getEventListeners returns a dictionary of events associated with the element.
function removeEventListener(el, eventName) {
if (!el) {
throw new Error('Invalid DOM reference passed');
}
const listeners = getEventListeners(el)[eventName] || [];
listeners.forEach(({
listener
}) => {
removeEventListener(eventName, listener);
});
}
function removeAllEventListener(el) {
if (!el) {
throw new Error('Invalid DOM reference passed');
}
const events = Object.entries(getEventListeners(el) || {});
events.forEach(([eventName, listeners]) => {
listeners.forEach(({
listener
}) => {
removeEventListener(eventName, listener);
});
});
}
// example
// remove mouseout event
removeEventListener(loginSection, 'mouseout');
// remove all event listeners
removeAllEventListener(loginSection);

Siblings pure JavaScript with show and hide, now jQuery

What is the best way of achieving this, vanilla JavaScript.
I really want to understand it.
Thanks so much.
$(duplicate[ii]).hide().siblings().show();
Assuming duplicate[ii] is a DOM element, you could loop the children of the parent of that element, and set the style of the actual element after the loop.
const d = duplicate[ii];
for (const el of d.parentNode.children) {
el.style.display = "block";
}
d.style.display = "none";
However, while that's basically a translation, I wouldn't write it that way. I'd use classes with the appropriate styling:
const d = duplicate[ii];
for (const el of d.parentNode.querySelectorAll(".hide")) {
el.classList.remove("hide");
}
d.classList.add("hide");
.hide {
display: none;
}
If you know only one element will have hide at any given time, you can drop the loop.
const h = duplicate[ii].parentNode.querySelector(".hide")
if (h) {
h.classList.remove("hide");
}
duplicate[ii].classList.add("hide");
If you do this frequently, I'd create a utility function that receives a class name, an element, and a collection. You could even enhance it so that the collection is optional, in which case you'd use the siblings.
function swapClass(clss, target, els) {
if (!els) {
els = target.parentNode.children;
}
for (const el of els) {
el.classList.remove(cls);
}
target.classList.add(cls);
}
Then its just like this:
swapClass("hide", duplicate[ii]);
const toArray = (list) => Array.prototype.slice.call(list)
const foo = (node) => {
let children = toArray(node.parentNode.children)
children.forEach(child => {
child.style.display = child === node ? 'none' : 'block'
})
}
If duplicate[ii] is a DOM , just foo(duplicate[ii])
If duplicate[ii] is a Css Selector, you can use
toArray(document.querySelectorAll(selector)).forEach(node => {
foo(node)
})
This answer is assuming node is is Block element

Categories