get a nested element inside a div by class name - javascript

I am trying to get an span element which has a class of main-tag inside a nested div. But i don't want to get that element through querySelector as there are many elements in my html file with the same class and i don't intend to use IDs for that.
I know i am making some mistake in my recursive function. That's why this code is not working.
So for this i'm recursively calling a function to get the desired element. But undefined is returned by found variable.
function getElem(cell, name) {
let found;
if (cell.classList.contains(name)) return cell;
else if (cell.children.length === 0) return null;
else {
found = Array.from(cell.children).find((element) =>
element.classList.contains(name)
);
if (found === undefined) {
Array.from(cell.children).forEach((element) => {
found = getElem(element, name);
});
} else return found;
}
}
console.log(getElem(document.getElementById("block-one"), "main-tag"));
<div id="block-one">
<div class="something-one">
<span class="something-two">Hello</div>
<span class="abc">not found</div>
</div>
<div class="here">
<span class="main-tag">Yes, i am here</div>
<span class="bogus-tag">never find out</div>
</div>
</div>

Your markup is using </div> where </span> is expected.
You can use full CSS selectors with querySelector. So for instance, to find the first element with the class main-tag inside the element with id="block-one", you can do this:
const element = document.querySelector("#block-one .main-tag");
Live Example:
console.log(document.querySelector("#block-one .main-tag"));
<div id="block-one">
<div class="something-one">
<span class="something-two">Hello</span>
<span class="abc">not found</span>
</div>
<div class="here">
<span class="main-tag">Yes, i am here</span>
<span class="bogus-tag">never find out</span>
</div>
</div>
Or alternatively, you can call querySelector on an element to only look at its descendants:
const element = document.getElementById("block-one").querySelector(".main-tag");
Live Example:
console.log(document.getElementById("block-one").querySelector(".main-tag"));
<div id="block-one">
<div class="something-one">
<span class="something-two">Hello</span>
<span class="abc">not found</span>
</div>
<div class="here">
<span class="main-tag">Yes, i am here</span>
<span class="bogus-tag">never find out</span>
</div>
</div>
A key difference between those is that the second one will throw an error if there is no id="block-one" element; the first will just return null. You could fix that using the newish optional chaining operator:
const element = document.getElementById("block-one")?.querySelector(".main-tag");
element will be either A) The matching element if there is one; B) null if there's an id="block-one" element but there are no .main-tag elements in it; or C) undefined if there is no id="block-one" element at all. (Even though document.getElementById returns null when it doesn't find something, the optional chaining operator results in undefined when its left-hand operand is either undefined or null.)
Or just use the document.querySelector option (the first one above), which gives you the matching element or null.
You don't seem to want this from your HTML, but: If you want to allow for the possibility the element itself has the class (getElem seems to allow for that possibility), you can use a group selector:
const element = document.querySelector("#block-one.main-tag, #block-one .main-tag");
Live Example:
console.log(document.querySelector("#block-one.main-tag, #block-one .main-tag"));
<div id="block-one" class="main-tag">
<div class="something-one">
<span class="something-two">Hello</span>
<span class="abc">not found</span>
</div>
<div class="here">
<span class="main-tag">Yes, i am here</span>
<span class="bogus-tag">never find out</span>
</div>
</div>
That works because the first member of the group, #block-one.main-tag (without a space) only matches the id="block-one" element if it has the class. If block-one doesn't have the class but one of its descendants does, that's found by the other member of the group, #block-one .main-tag (with the space). If the block-one element has the class and one of its descendants does, the block-one element is the one found, since it's first in document order (a parent is before its children in document order).

Your HTML is invalid.
When fixed, you can get the span using selectors directly
console.log(document.querySelector("#block-one > div.here > span.main-tag").textContent)
<div id="block-one">
<div class="something-one">
<span class="something-two">Hello</span>
<span class="abc">not found</span>
</div>
<div class="here">
<span class="main-tag">Yes, I am here</span>
<span class="bogus-tag">never find out</span>
</div>
</div>

Related

Jquery find() class with regular expression

I have div elements that have an icon inside, and I want to use the find() method to return which of the 4 types of icons is inside. Is there a way to use find() with a regular expression to return the class?
EDIT: I've updated the divs to show how the <i> is "buried" within the div and not a direct child
<div id="a" class="tile">
<span>
<span>
<i class="led-red"></i>
Led Red
</span>
</span>
</div>
<div id="b" class="tile">
<span>
<span>
<i class="led-green"></i>
Led Green
</span>
</span>
</div>
<div id="c" class="tile">
<span>
<span>
<i class="led-yellow"></i>
Led Yellow
</span>
</span>
</div>
var rexValues = /(led-green|led-yellow|led-red|led-null)/;
//this would work for element "a" if element "a" had the class attached,
//but because the class is buried inside I need to use find()
var aclass = a.className.match(rexValues);
//I'm looking to do something more like
var aclass = $(a).find(rexValues);
This is part of a sort function where I am sorting the divs based on their icon. The solution I'm basing off of is here: https://stackoverflow.com/a/46026203/9537489
Don't use regex when not needed
I am using the sort order from your linked answer
container can be document.body if you do not have a static container near the tiles, but then the tiles will be moved if you have other stuff in the container. Let me know if you need to replace in place
I use the [...nodeList] spread syntax because sort is not a native method of a nodeList. NOTE: the spread has to be on the querySelectorAll statement or the tiles will not retain their sort order
const container = document.body; // document.getElementById("container");
const sortOrder = { red: 0, orange: 1, yellow: 2, green: 3 };
const tiles = [...container.querySelectorAll(".tile")]; // needed for the .sort
const getClassStartingWith = (elem, cls) => [...elem.classList].filter(clss => clss.startsWith(cls))[0];
const prefix = "led-";
tiles.sort((a, b) => {
const aClass = getClassStartingWith(a.querySelector("i"), prefix).replace(prefix, "");;
const bClass = getClassStartingWith(b.querySelector("i"), prefix).replace(prefix, "");
console.log(aClass,sortOrder[aClass],bClass, sortOrder[bClass],sortOrder[aClass] - sortOrder[bClass])
return sortOrder[aClass] - sortOrder[bClass]
});
tiles.forEach(tile => container.appendChild(tile))
<div id="c" class="tile">
<span>
<span>
<i class="led-yellow"></i>
Led Yellow
</span>
</span>
</div>
<div id="b" class="tile">
<span>
<span>
<i class="led-green"></i>
Led Green
</span>
</span>
</div>
<div id="a" class="tile">
<span>
<span>
<i class="led-red"></i>
Led Red
</span>
</span>
</div>
the find method in jQuery doesn't support this. There is an extension that you could use to add this functionality
Are those classes with the prefix limited or unique? You could use the selector startsWith ([<attr>^=<value>], i.e. [class^=led-]). Here's the CSS selector reference if you don't want to add the jQuery based functionality

Identifying an HTML element that has *no* attributes of any kind with JavaScript?

I have a tool that is used for cleaning up crappy HTML in order to make sense of the underlying structure. Having stripped class, style attributes and various Angular attributes, often the resulting markup is a series of nested <div> or <span> elements that have no attributes. What I would like to do is provide option to do a second pass where a <div> or <span> with no attributes can be removed, to flatten the structure more.
Is there a way in JavaScript to confirm that an HTML element has no attributes of any kind?
And if that is possible, how might I approach this stripping of an element?
For example, assuming I have this:
<div>
<div>
<div id="blah">
<div>
<div>
<span dir="auto">
<span>Joe Bloggs</span>
</span>
</div>
</div>
</div>
</div>
</div>
That should end up as:
<div id="blah">
<span dir="auto">
Joe Bloggs
</span>
</div>
Which I would then format to:
<div id="blah">
<span dir="auto">
Joe Bloggs
</span>
</div>
So I'd need a function that can walk the DOM and remove a div (or span) that has no attributes while leaving the inner contents intact (unless of course any of those inner elements can also be stripped for same reason).
Any pointers before I go ahead and construct a shoddy (but working) script would be appreciated!
The attributes property will tell you how many attributes an element has.
const countAttributes = element => console.log({
count: element.attributes.length,
list: [...element.attributes].map(attribute => attribute.name)
});
const divs = document.querySelectorAll('div');
divs.forEach(countAttributes);
<div></div>
<div class="one attribute"></div>
<div class="two attributes" id="second attribute"></div>
Do note that an element without attributes might still be used for something (e.g. a stylesheet might reference it in relation to other elements).
Here's how I did it.
I created a demo element, to get the elements, then I checked the number of elements, I checked if the element should be stripped.
I replaced the element with its children, and if it didn't have any, I used its text
function strip(startElement, toStrip) {
const test = document.createElement('div');
test.innerHTML = startElement.outerHTML;
[...test.querySelectorAll('*')].forEach(elem => {
if (!elem.attributes.length && toStrip.includes(elem.tagName.toLowerCase())) {
if (elem.children.length) elem.replaceWith(...elem.children);
else elem.replaceWith(elem.innerText);
} ;
});
return test.innerHTML;
}
console.log(strip(document.querySelector('div'), ['span', 'div']));
<div>
<div>
<div id="blah">
<div>
<div>
<span dir="auto">
<span>Joe Bloggs</span>
</span>
</div>
</div>
</div>
</div>
</div>
Updated Code
Here you go.
document.querySelectorAll("div").forEach((ele) => {
if (ele.attributes.length === 0) {
var fragment = document.createDocumentFragment();
while (ele.firstChild) {
fragment.appendChild(ele.firstChild);
}
ele.parentNode.replaceChild(fragment, ele);
}
});
<div>
<div>
<div id="blah">
<div>
<div>
<span dir="auto">
<span>Joe Bloggs</span>
</span>
</div>
</div>
</div>
</div>
</div>
So final output would be
<div id="blah">
<span dir="auto">
<span>Joe Bloggs</span>
</span>
</div>

jQuery ID Return is undefined although HTML ID is defined

I'm trying to retrieve the ID of one element, store it as a variable and then use that ID value to interact with other elements in that section with the same ID.
<div class="mainContent">
<div class="articleContent">
<h1>header1</h1>
<p class="articlePara" id="one">para1</p>
</div>
<div class="articleFooter" id="one" onclick="readMore()">
</div>
</div>
<div class="mainContent">
<div class="articleContent">
<h1>header2</h1>
<p class="articlePara" id="two">para2</p>
</div>
<div class="articleFooter" id="two" onclick="readMore()">
</div>
</div>
And then the JS/jQuery
function readMore() {
var subID = event.target.id;
var newTarget = document.getElementById(subID).getElementsByClassName("articlePara");
alert(newTarget.id);
}
At this point I'm only trying to display the ID of the selected element but it is returning undefined and in most cases people seem to notice that jQuery is getting confused because of the differences between DOM variables and jQuery ones.
jsfiddle: https://jsfiddle.net/dr0f2nu3/
To be completely clear, I want to be able to click on one element, retrieve the ID and then select an element in the family of that clicked element using that ID value.
just remove the getElementsByClassName("articlePara"); in end of the newTarget .already you are call the element with id alert the element of the id is same with target.id
function readMore() {
var subID = event.target.id;
var newTarget = $('[id='+subID+'][class="articlePara"]')
console.log(newTarget.attr('id'));
console.log(newTarget.length);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="mainContent">
<div class="articleContent">
<h1>header</h1>
<p class="articlePara" id="one"></p>
</div>
<div class="articleFooter" id="one" onclick="readMore()">click
</div>
</div>
As you have read before, you should keep your id's unique, and you should avoid using onclick in html, but you could do it like this.
With querySelector you get the element and then with parentElement you can retrieve the parent of that element.
function readMore(el) {
var articleFooterId = el.id;
var articlePara = document.querySelector(".articleContent #"+articleFooterId);
var articleContent = articlePara.parentElement;
console.log('articleFooter', articleFooterId);
console.log('articlePara', articlePara);
console.log('articleContent', articleContent);
}
In your html you can return the 'this' object back to the function by doing readMore(this).
<div class="mainContent">
<div class="articleContent">
<h1>header1</h1>
<p class="articlePara" id="one">para1</p>
</div>
<div class="articleFooter" id="one" onclick="readMore(this)">footertext</div>
</div>
<div class="mainContent">
<div class="articleContent">
<h1>header2</h1>
<p class="articlePara" id="two">para2</p>
</div>
<div class="articleFooter" id="two" onclick="readMore(this)">footertext</div>
</div>
jsfiddle
if you're using Jquery:
$(function () {
$('div.articleFooter').click(function () {
var para = $(this).prev().find('p.articlePara').text();
alert('T:' + para);
});
})
$('.articleFooter').click(function() {
var b=subId; //can be any
var a="p[id="+b+"]"+"[class='articlePara']";
$(a).something;
});
You have forgotten to pass in event as parameter in your onclick= call in html.
In your javascript, you need to include event in the parenthesis as well.
window.readMore = function(event) {...}
if you write document.getElementById(subID).getElementsByClassName("articlePara"); That's saying you want to get your clicked element's CHILD elements that have class equal to articlePara . There is none. So you get undefined.
If you want to find all element with a ID one and a class articlePara, it can be done easily with jQuery:
newtarget = $("#one.articlePara");
You can insert a line: debugger; in your onclick handler function to trigger the browser's debugging tool and inspect the values of variables. Then you will know whether you are getting what you want.

Shouldn't getElementById return an object containing sub objects?

Using the following code I was expecting to get an object which contains the sub elements
var MemberDiv=document.getElementById("idProfileMainContainer");
console.log(MemberDiv.length);
Using the above code I get "undefined". I'm confused. I thought it should have an object array that contains all sub elements.
Ultimately I am trying to find the sub <div /> with the <span /> that has "Board Position" in it.
<div id="idContainer8592665" class="fieldContainer simpleTextContainer">
<div class="fieldSubContainer labeledTextContainer">
<div class="fieldLabel">
<span id="FunctionalBlock1_ctl00_ctl00_MemberForm_memberFormRepeater_ctl22_titleLabel" title="Cannot be edited, Administrator access only">Board Position</span>
</div>
<div class="fieldBody">
<span id="FunctionalBlock1_ctl00_ctl00_MemberForm_memberFormRepeater_ctl22_DropDownLabel8592665">Webmaster</span><input name="FunctionalBlock1$ctl00$ctl00$MemberForm$memberFormRepeater$ctl22$ctl03" type="hidden">
</div>
</div>
</div>
The return value of getElementById is not an array, but one node (or none) as you can find on MDN:
Returns a reference to the element by its ID
To get the element you are targetting, you could use this ES6 script:
// Select all div.fieldLabel descendants of #idContainer8592665 and find one that has
// the text content we look for.
var div = Array.from(document.querySelectorAll('#idContainer8592665 div.fieldLabel'))
.find( div => div.textContent.includes('Board Position'));
// Show the text of the div element we found
console.log(div.textContent.trim());
<div id="idContainer8592665" class="fieldContainer simpleTextContainer">
<div class="fieldSubContainer labeledTextContainer">
<div class="fieldLabel">
<span id="FunctionalBlock1_ctl00_ctl00_MemberForm_memberFormRepeater_ctl22_titleLabel" title="Cannot be edited, Administrator access only">Board Position</span>
</div>
<div class="fieldBody">
<span id="FunctionalBlock1_ctl00_ctl00_MemberForm_memberFormRepeater_ctl22_DropDownLabel8592665">Webmaster</span><input name="FunctionalBlock1$ctl00$ctl00$MemberForm$memberFormRepeater$ctl22$ctl03" type="hidden">
</div>
</div>
</div>
In ES5 compatible script it would look like this:
// Select all div.fieldLabel descendants of #idContainer8592665 and find one that has
// the text content we look for.
var parent = document.getElementById('idContainer8592665');
var div = [].filter.call(parent.getElementsByClassName('fieldLabel'),
function (div) {
return div.textContent.indexOf('Board Position') != -1;
}).pop();
// Show the text of the div element we found
console.log(div.textContent.trim());
<div id="idContainer8592665" class="fieldContainer simpleTextContainer">
<div class="fieldSubContainer labeledTextContainer">
<div class="fieldLabel">
<span id="FunctionalBlock1_ctl00_ctl00_MemberForm_memberFormRepeater_ctl22_titleLabel" title="Cannot be edited, Administrator access only">Board Position</span>
</div>
<div class="fieldBody">
<span id="FunctionalBlock1_ctl00_ctl00_MemberForm_memberFormRepeater_ctl22_DropDownLabel8592665">Webmaster</span><input name="FunctionalBlock1$ctl00$ctl00$MemberForm$memberFormRepeater$ctl22$ctl03" type="hidden">
</div>
</div>
</div>
What I've done is get the collection of span elements. I then step through this collection, checking if the textContent is 'Board Position'. If so, I walk back up the DOM tree until I reach a div node. I then print it's class (the only attribute it has in your html) and also, the outerHTML.
If there may be more that one matching element, result should be initialized to [], you should check for .length > 0 instead of == undefined, you should push the element into result result.push(elem) instead of setting it to the elem with result = elem and finally, you should iterate through the results, rather than showing the only single one.
function byId(id){return document.getElementById(id)}
function allByTag(tag,parent){return (parent == undefined ? document : parent).getElementsByTagName(tag)}
// useful for HtmlCollection, NodeList, String types - (array-like objects)
function forEach(array, callback, scope){for (var i=0,n=array.length; i<n; i++)callback.call(scope, array[i], i, array);} // passes back stuff we need
window.addEventListener('load', onDocLoaded);
function onDocLoaded(evt)
{
var spans = allByTag('span');
var result=undefined;
forEach(spans, findParentIfMatching);
if (result != undefined)
{
console.log(result.className);
console.log('*******************************');
console.log(result.outerHTML);
}
function findParentIfMatching(elem,index,collection)
{
if (elem.textContent == 'Board Position')
{
while (elem.nodeName != 'DIV')
elem = elem.parentNode;
result = elem;
}
}
}
<div id="idContainer8592665" class="fieldContainer simpleTextContainer">
<div class="fieldSubContainer labeledTextContainer">
<div class="fieldLabel">
<span id="FunctionalBlock1_ctl00_ctl00_MemberForm_memberFormRepeater_ctl22_titleLabel" title="Cannot be edited, Administrator access only">Board Position</span>
</div>
<div class="fieldBody">
<span id="FunctionalBlock1_ctl00_ctl00_MemberForm_memberFormRepeater_ctl22_DropDownLabel8592665">Webmaster</span><input name="FunctionalBlock1$ctl00$ctl00$MemberForm$memberFormRepeater$ctl22$ctl03" type="hidden">
</div>
</div>
</div>

Unable to access descendant of an HTML element using jQuery

I have a problem with getting a html() value of child of a parent :D
function voteup(e){
var count = $(e).parents('.item').children('.count');
console.log(count.html()); // undefined
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="post_contain">
<img src="images/comments/dQ6dz.jpg" alt="">
</div>
<div class="item">
<p class="post-meta">
<span href="/gag/agVzE1g" target="_blank">
<span class="count">5</span>points
</span>
</p>
<div class="vote">
<ul class="btn-vote left">
<li class="badge-item-vote-up-li">
<a onclick="voteup(this)">Click me</a>
</li>
</ul>
</div>
</div>
In the function voteup(e), I need to get the value of the class 'count', but I don't retrieve the value with html()
children only traverses a single level of the DOM tree - i.e. it won't find grandchildren.
Instead, use closest to find the .item -- which finds the single nearest match, as opposed to parents which can find multiple -- and find to locate the child, since that will traverse arbitrarily deep HTML structures:
function voteup(e){
var count = $(e).closest('.item').find('.count');
alert(count.html());
var actual = parseInt(count.html(), 10);
count.text(actual + 1);
}

Categories