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
Related
I have a createEelement() function.
With her, I will create HTML elements.
Function code:
function createElement(tagName, options, ...children) {
const { classNames = [], attributes = {} } = options;
const elem = document.createElement(tagName);
for (let i = 0; i < classNames.length; i++) {
elem.classList.add(classNames[i]);
}
for (const attributePair of Object.entries(attributes)) {
const [attributeKey, attributeValue] = attributePair;
elem.setAttribute(attributeKey, attributeValue);
}
elem.append(...children);
return elem;
}
The function allows you to create items, hang classes and attributes. And also encourage all children to the item. Allows you to do it immediately when creating. I need to expand this feature in such a way that the event handlers can also be mounted. I plan to implement this in the options object.
My question is how can this be done? Considering that listeners may be more than one ...
To resolve your problem, you could have an array of object, with the names of the events, the callbacks and the options of the event :
const listeners = [{
name: "onclick",
callback: (e) => e.preventDefault(),
options: {},
}];
(This is just an example)
Here is the result :
function createElement(tagName, options, ...children) {
const { classNames = [], attributes = {}, listeners = {} } = options;
const elem = document.createElement(tagName);
for (let i = 0; i < classNames.length; i++) {
elem.classList.add(classNames[i]);
}
for (const listener of listeners) {
elem.addEventListener(...listener);
}
for (const attributePair of Object.entries(attributes)) {
const [attributeKey, attributeValue] = attributePair;
elem.setAttribute(attributeKey, attributeValue);
}
elem.append(...children);
return elem;
}
Bonus
Here are some factorisation improvments :
function createElement(tagName, options, ...children) {
const { classNames = [], attributes = {}, listeners = {} } = options;
const elem = document.createElement(tagName);
// using a for...of loop
for (const className of classNames) {
elem.classList.add(className);
}
for (const listener of listeners) {
elem.addEventListener(...listener);
}
// destructuring instead of recreating two variables
for (const attributePair of Object.entries(attributes)) {
elem.setAttribute(...attributePair);
}
elem.append(...children);
return elem;
}
PS : I didn't tested this code so it might not work, tell me if something is wrong.
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>
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)
I am trying to build a function that checks if all fields are populated, if populated then show div if not hide.
I can get this to work on one fields however i have then tried two ways of checking multiple.
first
if first condition met I then ran other condition checking second field nested inside the first... this done not work.
second
I passed in an array of ID's rather than a single... this did not work either..
I am left with a working function that only works if first filed is populated can anyone think of a solution to this or maybe i passed in my array incorrectly.
My code
var myVar = setInterval(myTimer, 10);
function myTimer() {
if(!document.getElementById('Email').value) { // I need this to pass if multiple id's
var divsToHide = document.getElementsByClassName("somediv"); //divsToHide is an array
for(var i = 0; i < divsToHide.length; i++){
divsToHide[i].style.visibility = "hidden"; // or
divsToHide[i].style.display = "none"; // depending on what you're doing
}
}
else {
var divsToHide = document.getElementsByClassName("somediv"); //divsToHide is an array
for(var i = 0; i < divsToHide.length; i++){
divsToHide[i].style.visibility = "visible"; // or
divsToHide[i].style.display = "block"; // depending on what you're doing
}
}
}
Make it so your function takes an argument of the element ID and the class Name you need to check for.
Also, never use .getElementsByClassName() (read here for why). Instead, use .querySelectorAll().
And, you can use the modern .forEach() API of arrays and node lists (not in IE though), which is simpler than managing traditional for loops with indexes.
Lastly, use pre-made CSS classes instead of inline styling.
// You just need to pass the ID and Class to the following line
var myVar = setInterval(function(){ myTimer([id here],[class here]) }, 10);
function myTimer(id, class) {
// Set up these references just once and the rest of the code
// will be easier to read
var elem = document.getElementById(id);
var divsToHide = document.querySelectorAll("." + class);
// Instead of negative logic, reverse the if branches
if(elem.value) {
divsToHide.forEach(function(){
this.classList.remove("hidden"); // Much simpler than inline styling
});
} else {
divsToHide.forEach(function(){
this.classList.add("hidden");
});
}
/* Use pre-made CSS classes instead of inline styling */
.hidden { display:none; }
If you have an array of the IDs such as
let idArray = ['foo', 'bar', 'baz']
You can iterate through an array using a for loop
for (i = 0; i > idArray.length; i++) {
if (!document.getElementById(idArray[i]).value) {
// your hide logic
} else {
// your show logic
}
}
You can create a const of all elements that need to validate. For example,
const elementIdsToBeValidated = ['name', 'email'];
You can also create validator functions that returns true and false based on input,
const nameValidator = (val) => !!val;
const emailValidator = (email) => !!email;
const validators = [nameValidator, emailValidator];
Then you can run your timer function,
var myVar = setInterval(myTimer(['name', 'email']), 10);
function myTimer(ids) {
ids.forEach(id => {
const el = document.getElementById(id);
const val = el.value;
const divEl = document.getElementById('error');
const valid = validators.reduce((acc, validator) => validator(val), false);
if(valid) {
divEl.style.display = 'none';
} else {
divEl.style.display = 'block';
}
});
}
You can look at this stackBlitz example,
https://stackblitz.com/edit/js-ie7ljf
I'm just trying to convert jQuery method to VanillaJS as below.
jQuery:
elements.parent(".item").removeClass("item");
Vanilla :
var parent = elements.parentNode;
parent.querySelector(".item").classList.remove('item');
But the Vanilla script is not working as same as jQuery. Someone please suggest any better solution.
Given that elements is plural, I'm assuming it's a collection, so use .forEach(). (This needs to be patched in legacy browsers unless elements is an actual Array)
elements.forEach(function(el) {
el.parentNode.classList.remove("item");
});
Note that you do not need to filter down to just the .item elements. If it doesn't have the class, it'll be a no-op.
Also note that .querySelector only searches for descendants. If you did need to filter on the class for other reasons, you'd use .matches.
elements.forEach(function(el) {
var par = el.parentNode;
if (par.matches(".item")) {
// Do work with the `.item` element
par.classList.remove("item");
}
});
If there could be multiple ancestors with the .item class, then traverse those ancestors in a nested loop.
elements.forEach(function(el) {
var par = el.parentNode;
do {
if (par.matches(".item")) {
// Do work with the `.item` element
par.classList.remove("item");
}
} while((par = par.parentNode));
});
You could make a helper function if you're doing this frequently.
function ancestors(el, filter) {
var res = [];
var par = el.parentNode;
do {
if (!filter || par.matches(filter)) {
res.push(par);
}
} while((par = par.parentNode));
return res;
}
So then it's like this.
elements.forEach(function(el) {
ancestors(el, ".item")
.forEach(function(par) { par.classList.remove(".item"); });
});