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"); });
});
Related
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
I am trying to convert this code below to vanilla javascript because we cannot use jQuery
if (isSafari) {
$('.buttonClassName').click(function() {
// do something
});
}
I was trying something like this, but it doesn't work:
if (isSafari) {
document.getElementByClassName("buttonClassName").onclick = function () {
// do something
};
}
What javascript can I use without jQuery
var elements = document.getElementsByClassName('buttonClassName');
for(var i = 0; i < elements.length; i++) {
var element = elements[i];
element.onclick = function() {}
}
I find it useful to build an array of my query selection so I can use Array prototype functions on the element collection. This selector function will be useful if you can't use jQuery:
function elem (selector) {
return Array.apply(this, document.querySelectorAll(selector));
}
elem('.buttonClassName').forEach(function (el) {
el.onclick = fn;
});
use getElementsByClassName
document.getElementsByClassName('buttonClassName')[0].onclick = myFunc;
See demo: http://jsbin.com/nihovadoto/edit?html,js,output
Everything looks close . I suggest caching your button(s) and trying an event listener like below
Example
var buttons = document.getElementsByClassName('buttonClassName');
buttons.addEventListener('click' , doSomething);
Hope this helps
My Collection has following structure
{
_id:1,
parent_id:0
}
{
_id:2,
parent_id:1
}
{
_id:3,
parent_id:1
}
{
_id:4,
parent_id:3
}
Like
a > a1 > a1-1,a1-2,a1-3 > a11
One parent has many children and the childrens can have many childrens infinite loop
but when user clicks on a I want to delete all its children and it childrens too
I tried following function
var deleteChildCards = function(id){
var count=userCards.find({parent_id: id}).count();
if(count > 0){
userCards.find({parent_id: id}).forEach(function (card) {
deleteChildCards(card._id);
});
}
else{
return userCards.remove({_id: id});
}
}
If I pass id to the function it must remove all it's childs,This is not removing all docs what is wrong in this function.
Is there any other way I can write this function?
In your example, the else portion of the if-else will never get run. Try the following adjustment:
var deleteChildCards = function(id) {
userCards.find({parent_id: id}).forEach(function(card) {
deleteChildCards(card._id);
userCards.remove(card._id);
});
}
You can completely eliminate the if-else in this example since the forEach won't iterate if there are no userCards found.
From my understanding, you did if else by mistake.
Try
var deleteChildCards = function(id){
var count=userCards.find({parent_id: id}).count();
if(count > 0){
userCards.find({parent_id: id}).fetch().forEach(function (card) {
deleteChildCards(card._id);
});
}
userCards.remove({_id: id});
}
Otherwise, it will only remove the cards at the lowest level of the hierarchy.
Also, you need to do .fetch() after find(), if you want to do forEach
I'm trying to create a function using vanilla JS that will:
Create a new DOM element
Assign it a Class Name
Place it in the DOM either appending to an existing div or inserting it specifically into the DOM if required using "insertBefore()"
I have come up with the somewhat inelegant solution below:
function createDomElem(elem, className, parent, refElement, type) {
var a = document.createElement(elem);
if (type == "append") {
document.querySelector(parent).appendChild(a);
} else if (type == "insert") {
document.querySelector(parent).parentNode.insertBefore(a, refElement)
}
a.className = className;
};
My problems with this solution are
Too many arguments to be passed
If not passing "insert" then you don't require refElement and to avoid "type" being mistaken for "refElement" you'd have to pass "refElement" as "null" and then define type as "append"
So my question is where can I streamline this function to become more useful within my program?
I'm also dreaming of the ability to be able to push child divs into the newly created div right within this function, defining how many child divs you would want and then using a for loop to append or insert these. Would this be better placed in a new function though?
I would split the code into two parts, as they have to separate concerns. I use something similar to the following for creating DOM elements:
var DomFactory = (function (document) {
var api = {
element: function (name, attributes) {
var el = document.createElement(name);
if (attributes) {
for (var key in attributes) {
if (attributes.hasOwnProperty(key)) {
el.setAttribute(key, attributes[key]);
}
}
}
return el;
},
div: function (attributes) {
return api.element('div', attributes);
}
};
return api;
}(window.document));
Usage:
var div = DomFactory.div({ 'class': 'hero' });
var table = DomFactory.element('table', { 'class': 'table table-bordered' });
Then for positioning, you could have a generalised position function:
function attach(source, target, position) {
switch (position) {
case 'before': {
target.parentNode.insertBefore(source, target);
break;
}
case 'after': {
if (target.nextSibling) {
target.parentNode.insertBefore(source, target.nextSibling);
} else {
target.parentNode.appendChild(source);
}
}
}
}
Usage:
attach(table, div, 'before');
I am using Drupal's Webform module which writes all the ids and classes automatically, so I can't add an Id to the place I want to add it. And Drupal doesn't put one in for me. Grrr.
I have no problem when I use my code with just a simple getElementById('myid'), but the element I need is a blank, no id, no class "a" that's inside a "legend" (which has a class but no id) that's inside a fieldset with an id.
I tried this code and several variations but it didn't work:
document.getElementById('webform-created-id-here').getElementsByTagName('legend').getElementsByTagName('a');
I feel like I'm not understanding how to properly access that part of the DOM. Can anyone offer help or suggestions?
Thank you!
getElementsByTagName return a nodelist with the elements found, so you must select the index of the element
Example for the first element
var test = document.getElementById('webform-created-id-here').getElementsByTagName('legend')[0].getElementsByTagName('a')[0];
DEMO VIEW
Recurse through the DOM yourself. References to the children of any element are stored in the childNodes attribute which exist for every node.
function recurseDOM (el,test) {
if (test(el)) {
return el;
}
else {
var children = el.childNodes;
var result;
for (var i=0; i<children.length; i+) {
result = recurseDOM(children[i],test);
if (result) {
return result;
}
}
}
return null;
}
This is only one possible implementation that I wrote in the 2 minutes it took me to type out this answer. So it can probably use some improvements. But you get the idea.
Use it like this:
recurseDOM(document.body,function(el){
if (el == something_something) { return 1}
return 0
});
You can write a similar function to test parents:
function testParents (el, test) {
var parent = el.parentNode;
if (test(parent)) {
return 1;
}
else {
if (parent != document.body) {
return testParents(parent,test);
}
}
return 0;
}
So you can write something like:
recurseDOM(document.body,function(el){
if (el.tagName == 'a' && testParents(el,function(p){
if (p.tagName == 'legend' && testParents(p,function(pp){
if (pp.id == 'webform-created-id-here') {
return 1;
}
return 0;
})) {
return 1;
}
return 0;
})) {
return 1;
}
return 0;
});
Alternatively, you can start the recursion form the getElementById instead of document.body.