Select every element that isn't a parent to any elements - javascript

I am developing a game that glitches at some point through using the CSS filter: invert(1); property. However, when you use that property on body, it makes everything position: absolute;. This is not good because I need most elements to be fixed, and everything goes to a negative top and not visible. How can I effectively get all elements in a list that isn't a parent to any other elements, but included if it has text? Any answers or other stack overflow topics would be nice!
Here is some of my code:
// In a working loop called Repeat()
if(Glitch == 1) {
document.querySelector(".ChangableStyles").innerHTML = "* {filter: invert(1)}"
} else {
document.querySelector(".ChangableStyles").innerHTML = ""
}
Edit: Since all of you are asking, the .ChangableStyles tag is a style element. The filter on everything applies when I change the innerHTML of that style tag to valid CSS styles. I don't want to be rude, but I have the .ChangableStyles thing figured out. Thank you.

You mention you already have a list of elements, but it's not clear how you're generating that list. I've gone ahead on the assumption you're wanting to "select" all elements in <body></body> that don't have any children.
You can use a combination of Array.from(), your pre-existing selection logic, and a filter() using node.childElementCount === 0 to accomplish what you describe. However on higher-complexity DOMs this will be computationally expensive, so I would implore you to re-consider your design instead of opting for this route. To be clear, this will meet your requirement of selecting ANY Node in the DOM which has no child elements ("isn't a parent to any other elements"), which includes any script, style or other "user-invisible" nodes in the body.
document.getElementById('get-elements-button').addEventListener('click', function () {
console.log(Array.from(document.body.getElementsByTagName("*")).filter(function (node) {
return node.childElementCount === 0;
}));
});
<div class="has-child-elements">
This is a child element
</div>
<div class="has-no-child-elements">
</div>
<div class="has-child-elements">
This is also a child element
</div>
<button id='get-elements-button'>Get elements with child elements →</button>

Related

JQuery how to find the closest element that is neither a parent nor a child of the current element?

Say I have HTML that looks like this:
<div>
<div>
<div class="calendar start">
</div>
</div>
<div>
<div class="calendar end">
</div>
</div>
</div>
We can assume that the start and end will always be on the same "level" of a branch from each other, and will at some point share a common parent.
Without knowledge of the exact HTML structure, how would I find calendar end from calendar start? What if they are nested further down?
Edit: For clarification. I want to start at start's parent. Search all child elements for end. Then move to the next parent, and search all child elements...etc till I find end. I am wondering if this is possible with built in JQuery functions, without writing my own DOM traversal logic.
You can do it like below, But it is a costlier process.
var parentWhichHasCalEnd =
$($(".calendar.start").parents()
.get().find(itm => $(itm).find(".calendar.end").length));
var calEnd = $(".calendar.end", parentWhichHasCalEnd);
DEMO
Explanation: We are selecting the .start element first, then we are retrieving its parent elements. After that we are converting that jquery object collection to an array of elements by using .get(). So that we could use .find(), an array function over it. Now inside of the callBack of find we are checking for .end over each parent element of .start, if a parent has .end then we would return that parent. Thats all.
You could get more understanding, if you read .get(), .find(), and arrow functions.
You can use jQuery#next() method from .start parent element
var startSelector = $('body > div > div:nth-child(3) > .start')
var endSelector = secondStart.parent().next().find('.end');
I think this method is faster rather than jQuery#children() method, but you can benchmark it if you want to
btw you may check my answer based on this JSBin
i don't know if i got this right but have you tried children function in jquery
$( ".calender" ).children( ".end" )
and for the parent you can use parent() function so you can first check the parent then the children or vicversa
edit:
if you dont know the exact structure the better way is to find the common parent and then search it's children :
$( ".calender.start").closest('.common-parent').children('.calender.end');
closest function give the nearest parent
Try:
$('.start').parent().parent().find('.end');

How to select a DIV if its child has any children?

I'm trying to select any divs on a page if a certain child of theirs has any children of its own.
Here's how the structure looks:
<div id="ID-SOME_LONG_ID">
<div class="GK">
<div id="SOME_LONGID_#1434646398866197"></div>
</div>
</div>
So I want to select all divs with id ID-SOME_LONG_ID only if the GK DIV has any children. It may or may not.
ID- stays the same and SOME_LONG_ID changes with each one.
The other one SOME_LONG_ID is the same on as the parent, and after the # it's a 16 digit number that is random.
Would using Regex be a good idea to look for them or maybe using jQuery's .children() like $( ".GK" ).children()?
Thank you!
Use :has(), :empty, and :not()
$('#ID-SOME_LONG_ID:has(.GK:not(:empty))')
However, note, :empty will fail if you want real children without text nodes. In that case you can do
$('.GK').filter(function() {
return $(this).children().length > 0;
});

How can I manipulate the DOM without a library like jQuery? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
I am used to using jQuery for manipulating the DOM, for example:
var mything = $("#mything");
mything.on("click", function() {
mything.addClass("red");
mything.html("I have sinned.");
});
But now I want to do the same things with Vanilla JavaScript. Is this possible? How can I do it?
Notice: This question is intended to be a comprehensive resource on Vanilla JavaScript DOM manipulation.
When manipulating the Document Object Model with Vanilla JS, you will be directly be accessing the Document and Nodes. A document contains Elements, particularly HTMLElements and SVGElements which are both Nodes. An Element may contain Text too.
Finding Elements
You can get the first element which matches a CSS selector with mynode.querySelector(), and all elements that match the selector with myNode.querySelectorAll(). Most of the time myNode will be Document, so you can get anything in the document which matches the selector – however, you can look through only a node's descendants when myNode is a an element.
document.querySelectorAll('p:hover'); // Returns a NodeList of hovered paragraphs
This is similar to jQuery('p:hover').
There are also more specialized methods like:
myNode.getElementById()
myNode.getElementsByTagName()
myNode.getElementsByClassName()
myNode.getElementsByName()
Which have self-explanatory names. Notice that .getElementBy... returns a single element while .getElementsBy... (plural elements) returns a NodeList, which is essentially an array of nodes, but it doesn't have the standard array methods.
See also: What's the best way to loop through a set of elements in JavaScript?
Each element may also have a:
parentNode
previousSibling
previousElementSibling (excludes text nodes)
nextSibling
nextElementSibling (excludes text nodes)
firstChild
firstElementChild (excludes text nodes)
lastChild
lastElementChild (excludes text nodes)
childElementCount (same as children.length)
And NodeLists of:
childNodes
children (excludes text nodes)
In this way, we can traverse the DOM.
For example, to get the last child of the first paragraph element in the parent of #clickme here:
document.getElementById('clickme').addEventListener('click', function() {
console.log(this.parentNode.getElementsByTagName('p')[0].lastChild);
});
<div>
<blockquote>This is a really great quote.</blockquote>
<p>This is a <em>really</em> interesting paragraph. <span>this will be selected</span></p>
<p>In fact, here's another!</p>
<button id="clickme">Click me!</button>
</div>
...you find its parentNode, use getElementsByTagName on that to only get paragraph descendants, take the first one of those, and get its lastChild.
To get the text contained in it, you could get its text node (its first child) then use text.wholeText.
Creating & Deleting
You can create an element with document.createElement('aTagName') or clone another one with newElement = myElement.cloneNode(). Pass cloneNode true as it's first argument to also duplicate its descendants. Don't clone elements with an ID because it will cause 2 elements with the same ID to appear in the same document.
You can then append the new element (or an existing one) to a parent element using parent.appendChild(newElement) or append it after another element with parent.insertBefore(newElement, referenceElement). An insertAfter method doesn't exist, but it can be created:
HTMLElement.prototype.insertAfter = function(newEl, refEl) {
if (refEl.nextSibling) refEl.parentNode.insertBefore(newEl, refEl.nextSibling);
else refEl.parentNode.appendChild(newEl);
};
A node can be removed with parent.removeChild() or replaced with parent.replaceChild(newChild) or just removed inline with mynode.remove().
function poof() {
this.remove();
}
var elements = document.getElementsByClassName('poof');
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', poof);
}
<span class="poof">hi,</span>
<span class="poof">click</span>
<span class="poof">to</span>
<span class="poof">delete</span>
<span class="poof">me;</span>
<span class="poof">it</span>
<span class="poof">was</span>
<span class="poof">fun</span>
<span class="poof">being</span>
<span class="poof">a</span>
<span class="poof">span</span>
Classes and styles
By "and styles," I mean just classes. Styles are for CSS. You can apply CSS styles only to elements who have had a class added with JavaScript.1
Elements in HTML have a classList property which is a DOMTokenList representing a space-separated property, in this case class. You can .add(), .remove(), and .toggle() classes in the classList or check if it .contains() a class.
document.getElementById('clickme').addEventListener('click', function() {
document.getElementById('colors').classList.toggle('green');
});
.green { color: green }
<div id="colors">hello!</div>
<button id="clickme">Click me!</button>
Attributes
Elements with certain attributes can be selected with querySelector and querySelectorAll. Most attributes are properties of the element you're working with already. For example:
myDiv.hidden = true; // Hides element from view and from screenreaders
But if they're not, any attribute can be accessed with getAttributeNode, setAttributeNode, and removeAttributeNode. AttributeNodes have ownerElements and values.
"data-*" attributes can be accessed with myelement.dataset. For example, mydiv.dataset.pie = 'yummy' would add data-pie="yummy" to the div.
Events
Events are slightly more complicated. Binding one (like jQuery('selector').on) is pretty easy:
myElement.addEventListener('event-name', afunction);
(Other objects also have this method – for example, window)
Events can also be removed:
myelement.removeEventListener('event-name', afunction);
See: removeEventListener
An event list can be found here.
The function passed to the addEventListener will be passed an argument of the event occurring and have a this of the element the event listener is bound to.
However, events aren't this simple: something as trivial as clicking on a button may fire many event listeners on different elements and for different events.
– Browser Input Events: Can We Do Better Than The Click? by Smashing Magazine
See also: What is event bubbling and capturing?
1 If you really need to modify a style with JS, use myElement.style.styleProperty = 'value' to change the inline style attribute.

Selecting the parent element that is hiding its children

I feel like this could have been asked before, but I can't seem to find it, so I'll ask myself.
I want to select the parent element that is the "cause" of its children being hidden. For example, a group of elements are essentially "hidden" but not because they have display:none but because some parent along the way has display:none.
If I only have a child element and I know that it is hidden, how might I easily find the parent that is causing it to be hidden.
I realize one solution is just to recursively loop through the parents such as...
function findHiddenParent(el){
var $el = $(el);
if($el.css('display') == 'none'){
return $el;
}
return findHiddenParent($el.parent());
}
Note I haven't tested the above code, its just for conceptualizing a solution
But is there an easier way, perhaps through some selector magic?
You can use .parents() then filter that to the last element that is hidden.
$(child).parents(":hidden").last().show();
it will select the parent element highest in the hierarchy that has display: none
Demo: http://jsfiddle.net/X9W2v/
Note, :hidden will also select elements with a width/height of 0.
Here's something similar to what you've got:
function findHiddenParents ( el ) {
return $(el).parents().filter(function () {
return $(this).css('display') == 'none';
});
}
This'll return all ancestors that are hidden. If you only want the closest or the farthest, you can just return it with .first() or .last() respectively.
If you're not actually interested in which elements are hidden, but just want to reveal them all, then you can't get any simpler than this:
$(el).parents().show()
This is how I would do it in plain JavaScript. Somehow I feel it's cleaner than the jQuery solutions that involve loops or callbacks (Kevin B's one-liner looks great!). The cons are, it's longer, and it doesn't check computed styles:
<div id="container">
<div id="a" style="display: none;">A
<div id="b">B
<div id="c">C</div>
</div>
</div>
</div>
var currentNode = document.getElementById('c');
while(currentNode.parentNode && currentNode.style.display !== 'none') {
currentNode = currentNode.parentNode;
}
alert(currentNode.id);
http://jsfiddle.net/nc4h2/

How do I remove descendant elements from a jQuery wrapped set?

I'm a little confused about which jQuery method and/or selectors to use when trying to select an element, and then remove certain descendant elements from the wrapped set.
For example, given the following HTML:
<div id="article">
<div id="inset">
<ul>
<li>This is bullet point #1.</li>
<li>This is bullet point #2.</li>
<li>This is bullet point #3.</li>
</ul>
</div>
<p>This is the first paragraph of the article</p>
<p>This is the second paragraph of the article</p>
<p>This is the third paragraph of the article</p>
</div>
I want to select the article:
var $article = $('#article');
but then remove <div id="inset"></div> and its descendants from the wrapped set. I tried the following:
var $article = $('#article').not('#inset');
but that didn't work, and in retrospect, I think I can see why. I also tried using remove() unsuccessfully.
What would be the correct way to do this?
Ultimately, I need to set this up in such a way that I can define a configuration array, such as:
var selectors = [
{
select: '#article',
exclude: ['#inset']
}
];
where select defines a single element that contains text content, and exclude is an optional array that defines one or more selectors to disregard text content from.
Given the final wrapped set with the excluded elements removed, I would like to be able to call jQuery's text() method to end up with the following text:
This is the first paragraph of the article.This is the second paragraph of the article.This is the third paragraph of the article.
The configuration array doesn't need to work exactly like that, but it should provide roughly equivalent configuration potential.
Thanks for any help you can provide!
I suppose you do not want to modify the original HTML by removing elements from it, but you want to just get the content of article without the inset.
Thats why I would use clone() to get a copy of the article and then remove the inset from it.
Like this:
$("#article").clone().find("#inset").remove().end().text()
$("#article") selects the article div, clone creates a
copy,
find gets the children to
remove (you could also use children),
remove(), removes the selected inset,
end() goes back to the original selection.
At the end I just added text() as you mentioned you wanted to do that.
if you want to remove anything in #article but #inset use:
$('#article > *:not(#inset)').remove() // selects all direct children of #article but not #inset and removes them
see an example here: http://jsfiddle.net/zwPsD/
if want to apply this rule to more then one DOM element you can chain them:
$('#article, #article2, #article3, #etc').find('> *').not('#inset, #that, #and. #there').remove()
you can find an example of this here:
http://jsfiddle.net/ZNjdE/
and with a simple each you can extract the text:
http://jsfiddle.net/ZNjdE/2/
Unless I am missing something, why can't you select all of the <p> elements within the article div?
$("#article p")
If that is unacceptable, I think you are looking for the filter function...
$("#article").filter(":not(#inset)")
Note: you can have multiple selectors within the :not() selector. They just have to be comma delimited, so this approach should accomodate your configurational needs.
Try something like this.
$('#article').children(':not(#inset)').each(function(){
alert($(this).text());
});
If you want to do it with an object:
var selectors = {
select: '#article',
exclude: ['#inset', 'p']
};
$(selectors.select).children(':not('+selectors.exclude.join(',')+')').each(function(){
alert($(this).text());
});
EDIT
To get any level of ancestor, you could add extra selectors and use find(). Eg.
$('#article').find('li:first, :not(#inset, #inset *)').each(function(){
alert($(this).text());
});
With this you'd be excluding #inset and all #inset's ancestors except the first li. It won't quite work with the selectors object from before though because you're excluding a group of elements and then including some of the excluded ones. You could do it with three elements in the object:
var selectors = {select: ... , exclude: ... , includeFromExcluded: ...};

Categories