Given a div such as <div class="abcd ws-compid rh03 a11y-true"></div>, I need to be able to find the last class on each div and pull it's value.
I can currently get all the classes with classList, but can't seem to get the last class in the DOMTokenList:
[...document.querySelectorAll('.ws-compid')].map(component => component.classList ? {id: component.innerText, type: component.classList} : null);
The class I need will always be the last one in the array, but options like .last() don't seem to work.
You can also work with .classList and turn it into an array:
document.querySelectorAll(".ws-compid").forEach(d=>console.log([...d.classList].at(-1)))
<div class="abcd ws-compid rh03 a11y-true">one</div>
<div class="abcd ws-compid rh03 a12y-false">two</div>
Array.from(
document.getElementsByTagName("div")
).map( i => ({
type: i.className.split(' ').pop(),
id: i.innerText
}))
create an array from all div Elements, them map to their last className (type) and value (id)
consider filtering those empty (not all tag have innerText or classes)
Figured out the solution, since it's actually an object, I needed to use Object.entries():
[...document.querySelectorAll('.ws-compid')].map(component => component.classList ? {id: component.innerText, type: Object.entries(component.classList).pop()[1]} : null);
Related
I want to ensure that when I click on the divs (A, B, C), the link of the button changes and gets the values of the data attributes in the appropriate places. I wrote a small script, but it does not work, and there is still not enough knowledge to understand exactly where I went wrong. Any help would be welcome.
document.getElementById("product").onclick = function() {
document.getElementById("purchase").href =
"/?add-to-cart=" + this.data-product +
"&variation_id=" + this.data-id + "/";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="product__items" id="product">
<div data-id="338" data-product="A" id="uI-1" class="items-uniqueItem">A</div>
<div data-id="339" data-product="B" id="uI-2" class="items-uniqueItem">B</div>
<div data-id="340" data-product="C" id="uI-3" class="items-uniqueItem">C</div>
<div class="product__items---btn">
Button
</div><!-- btn -->
</div>
You have several problems here.
First, I suggest you consult the documentation for HTMLElement.dataset or jQuery's .data().
Also, if you intend on using event delegation, you can't use this to refer to the event source element in a vanilla event listener as it will refer to the delegate.
Since you do have jQuery involved, you might as well use it since it makes this a lot easier (see also vanilla JS version below)
const button = $("#purchase")
$("#product").on("click", ".items-uniqueItem[data-id][data-product]", function() {
// Due to the selector above, `this` is now the clicked `<div>`
// Extract data properties
const { product, id } = $(this).data()
// Construct URL parameters
const params = new URLSearchParams({
"add-to-cart": product,
"variation_id": id
})
// Set the `href`
button.prop("href", `/?${params}/`)
})
/* this is just for visibility */
.items-uniqueItem{cursor:pointer;}#purchase{display:block;text-decoration:none;margin: 1rem;}#purchase:after{content:attr(href);display:block;color:#ccc;margin:.5rem;}
<!-- your HTML, just minified -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script><div class="product__items" id="product"><div data-id="338" data-product="A" id="uI-1" class="items-uniqueItem">A</div><div data-id="339" data-product="B" id="uI-2" class="items-uniqueItem">B</div><div data-id="340" data-product="C" id="uI-3" class="items-uniqueItem">C</div><div class="product__items---btn">Button</div></div>
A vanilla JS version would look something more like this. You can use Element.closest() to locate the delegated event source
const button = document.getElementById("purchase")
document.getElementById("product").addEventListener("click", e => {
// find the required event source element
const el = e.target.closest(".items-uniqueItem[data-id][data-product]")
if (el) {
// Extract data properties
const { product, id } = el.dataset
// Construct URL parameters
const params = new URLSearchParams({
"add-to-cart": product,
"variation_id": id
})
// Set the `href`
button.href = `/?${params}/`
}
})
.items-uniqueItem{cursor:pointer;}#purchase{display:block;text-decoration:none;margin: 1rem;}#purchase:after{content:attr(href);display:block;color:#ccc;margin:.5rem;}
<!-- your HTML, just minified -->
<div class="product__items" id="product"><div data-id="338" data-product="A" id="uI-1" class="items-uniqueItem">A</div><div data-id="339" data-product="B" id="uI-2" class="items-uniqueItem">B</div><div data-id="340" data-product="C" id="uI-3" class="items-uniqueItem">C</div><div class="product__items---btn">Button</div></div>
As you can see, it's not very different to the jQuery version so maybe you might not need jQuery
I've never personally used the element.onlick = function() {...} notation, so I'll be usingelement.addEventListener('click', (e) => ...), but it should work the same way.
What you are doing is selecting the object that has the id "product". But "product" is the parent os the elements you want to select.
If you want to select several elements and do something with them, you can't use the id attribute, since id is unique for html page. So you'll want to use classes for that.
Create a class and add that class to each child (the ones with the data-product).
Select all children with .querySelectorAll(). Here is the doc. This returns a NodeList, but it's similar to an Array.
Iterate thought the List with a .forEach(item => ...) where item represents each element of the list.
Add an Event Listener (or .click, I guess) on each item.
*theList*.forEach( (item) => {
item.addEventListener('click', (event) => {
event.target.href = "/?add-to-cart=" + event.target.dataset.product + "&" + "variation_id=" + event.target.dataset.id + "/";
})
));
To access a dataset in JS you use the .dataset property.
First, grab all the divs that have a given class so that we can use their data.
const items = document.querySelectorAll('.items-uniqueItem');
items.forEach(item => item.addEventListener('click', (e) => console.log(e.target)))
Then inside you click handler you can get the button reference and assign the properties you want to get from it.
I am trying to add a CSS class to each div on a page that contains the string Subject:
I tried
var elList = document.querySelectorAll("div");
elList.forEach(function(el) {
if (el.innerHTML.indexOf("Subject") !== -1) {
console.log(el);
el.setAttribute('class', "newClass");
}
});
but it didn't return any nodes. And also
var headings = document.evaluate("//*[contains(normalize-space(text()), 'Subject:')]", document, null, XPathResult.ANY_TYPE, null );
while(thisHeading = headings.iterateNext()){
thisHeading.setAttribute('class', "newClass");
console.log(thisHeading);
}
which returned an XPathResult that didn't seem to have any nodes as part of the object.
This is what the HTML looks like, although it is deeply nested inside the document body.
<div class="note-stream-header">Subject: Please Reply to This</div>
How can I select all nodes that contain a string and add a class to them with JS?
Your approach is fine, but since you are interested in the content of an element, use .textContent instead of innerHTML.
See additional comments inline.
// .forEach is not supported in all browsers on node lists
// Convert them to arrays first to be safe:
var elList = Array.prototype.slice.call(
document.querySelectorAll("div"));
elList.forEach(function(el) {
// Use .textContent when you aren't interested in HTML
if (el.textContent.indexOf("Subject") > -1) {
console.log(el);
el.classList.add("newClass"); // Use the .classList API (easier)
}
});
.newClass { background-color:#ff0; }
<div>The subject of this discussion is JavaScript</div>
<div>The topic of this discussion is JavaScript</div>
<div>The queen's royal subjects weren't amused.</div>
<div>Subject: textContent DOM property</div>
I have no option but javascript. I am sure lots of people went through this.
HTML code
<ul class="recordingslist"></ul>
..
..
..
<div class='test' style="display : none">
<ul class="recordingslist test1" ></ul>
</div>
..
..
..
<div class="testasdasdsad" style="display : none">
<ul class="recordingslist test2"></ul>
</div>
JS code
recordingslist = document.getElementsByClassName("recordingslist")[0];
I have many ul with the same class. Now only first ul is visible, I want to append "test" to that visible ul. How can we achieve this?
Thank you very much in advance.
Based on your clarification in the comments, you're searching for the first <ul> element whose parent <div> element has a display property which is not equal to none.
Given that, I'd suggest:
// here we use Array.from() to convert the supplied Array-like
// NodeList into an Array, in order to use Array methods:
Array.from(
// here we find all <ul> elements with a class of
// 'recordingslist':
document.querySelectorAll('ul.recordingslist')
// we filter the resulting Array of <ul> elements, using
// Array.prototype.filter():
).filter(
// using an Arrow function, in which the 'ul' variable
// is a reference to the current <ul> element of the Array
// of <ul> elements over which we're iterating:
ul => window.getComputedStyle(
// we find the computed CSS properties of the node:
ul.parentNode, null
// and access its 'display' property to ensure that
// it's computed display property-value is not equal
// to the value of 'none' (so it should not be hidden):
).display !== 'none'
// we iterate over those elements retained in the Array,
// using Array.prototype.forEach
).forEach(
// here we use another Arrow function:
// ul: the current <ul> in the Array of <ul> elements,
// index: the index of the current <ul> in the Array
// of <ul> elements.
// here if the index is exactly equal to 0, we add the
// 'test' class-name, otherwise we add an empty string:
(ul, index) => ul.classList.add( index === 0 ? 'test' : '' )
);
References:
Array.from().
Array.prototype.filter().
Array.prototype.forEach().
Arrow Functions.
Conditional, ternary, operator (assessment ? ifTrue : ifFalse).
Element.classList API.
Using jquery makes this quite a breeze. Suggest adding an additional attribute to your ul divs to mark if the div should be visible or not. Something like below
HTML
<ul visible=false class="recordingslist"></ul>
<ul visible=false class="recordingslist test1"></ul>
<ul visible=true class="recordingslist test2"></ul>
JS
let allElems = document.querySelectorAll('.recordingslist')
allElems.forEach((obj)=>{
if(obj.getAttribute('visible')==='true'){
obj.append('Hello there')
}
})
Use the jquery selector :visible for example, in your case, if you want to select only ul visibles:
$('ul:visible').append('test');
or using the class
$('.recordingslist:visible').append('test');
official docs: https://api.jquery.com/visible-selector/
I have this html codes example:
<html>
<body>
<div>
<div id="stophere">
<h4 class="parentclass">
<span class="target">Clicked</span>
</h4>
</div>
</div>
</body>
</html>
From the html codes example above, I want to get all parents' tag name of class target (when receiving click event) down from div with id stophere.
I tried this code:
$(ev.target).parents()
.map(function() {
return this.tagName;
})
.get()
.join( ", " );
But it includes all parents' tag names above stophere. While the result I want is only 1 div and 1 h4.
What is the correct way to get all parents of target down from stophere?
You can use the parentsUntil method for that
$(ev.target).parentsUntil($('#stophere').parent())
Note that it's non-inclusive, so we pass the parent of #stophere to include that element as well
FIDDLE
I don't claim that this is a good solution, but can be used if adeneo's solution is failed in your situation like in my case.
This code is checking whether the traversing limit is containing that limit line itself or not, by using find() method:
jQuery('html').on("click", function (ev) {
var elemparentid = jQuery(ev.target).closest("[id]").attr("id");
var thisparents = jQuery(ev.target).parents()
.map(function () {
// check if traversing limit is a children of current element or not, by using find() method
if (jQuery(this).find("#" + elemparentid).length < 1) {
return this.tagName;
}
}).get()
.join(", ");
alert(thisparents);
});
FIDDLE
so basically here is my script:
http://jsfiddle.net/JJFap/42/
Code -
jQuery(document).ready(function() {
var rel = new Array();
var count = 0;
jQuery(".setting").each(function() {
rel[count] = [];
if(jQuery("span").attr("rel")) {
rel[count].push(jQuery("span").attr("rel"));
}
console.log(count);
count++;
});
jQuery("body").text(rel);
console.log(rel);
});
and
<div class="setting">
<span rel="Variable">Variable</span>
<span rel="Item">Item</span>
<span rel="Something">Something</span>
</div>
<div>
<span rel="Smth">Smth</span>
<span>Sec</span>
</div>
<div class="setting">
<span>Second</span>
<span rel="first">First</span>
<span rel="Third">Third</span>
</div>
my question, is why does it display Variable, variable?
I would like it to display Variable, First, but I'm not able to do.
Basically what I would like to achieve is create new array, in which insert each div.setting span elements with rel attribute array.
So basically in this example it should output -
Array (
Array[0] => "Variable","Item","Something";
Array[1] => "first","Third";
)
Hope you understood what I meant :)
EDIT:
In my other example I tried to add jQuery("span").each(function() ... inside first each function, but it outputted two full arrays of all span elements with rel. I can't have different classes / ids for each div element, since all will have same class.
jQuery('span') is going to find ALL spans in your page, and then pull out the rel attribute of the first one. Since you don't provide a context for that span search, you'll always get the same #1 span in the document.
You should be using this:
jQuery('span',this).each(function() {
rel[count] = [];
if (jQuery(this).attr("rel")) {
rel[count].push(jQuery(this).attr("rel"));
}
console.log(count);
count++;
})
instead of this:
rel[count] = [];
if(jQuery("span").attr("rel")) {
rel[count].push(jQuery("span").attr("rel"));
}
console.log(count);
count++;
http://jsfiddle.net/mblase75/JJFap/52/
The trick is to use a second .each to loop over all the span tags inside each <div class="setting"> -- your original code was using jQuery("span"), which would just grab the first span tag in the document every time.
In addition to what has been said, you can also get rid of the count and one push() when using jQuery.fn.map() as well as getting rid of the if when adding [rel] to the selector:
jQuery(document).ready(function() {
var rel = [];
jQuery(".setting").each(function() {
rel.push(jQuery(this).find('span[rel]').map(function() {
return this.getAttribute('rel');
}).get());
});
jQuery("body").text(rel);
console.log(rel);
});
Within the .each() method, you have this code a couple times: jQuery("span").attr("rel"). That code simply looks for ALL span tags on the page. When you stick it inside the .push() method, it's just going to push the value for the rel attribute of the first jQuery object in the collection. Instead, you want to do something like $(this).find('span'). This will cause it to look for any span tags that are descendants of the current .setting element that the .each() method is iterating over.