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

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.

Related

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

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>

Why does jQuery return more than one element when selecting by type and ID? [duplicate]

I fetch data from Google's AdWords website which has multiple elements with the same id.
Could you please explain why the following 3 queries doesn't result with the same answer (2)?
Live Demo
HTML:
<div>
<span id="a">1</span>
<span id="a">2</span>
<span>3</span>
</div>
JS:
$(function() {
var w = $("div");
console.log($("#a").length); // 1 - Why?
console.log($("body #a").length); // 2
console.log($("#a", w).length); // 2
});
Having 2 elements with the same ID is not valid html according to the W3C specification.
When your CSS selector only has an ID selector (and is not used on a specific context), jQuery uses the native document.getElementById method, which returns only the first element with that ID.
However, in the other two instances, jQuery relies on the Sizzle selector engine (or querySelectorAll, if available), which apparently selects both elements. Results may vary on a per browser basis.
However, you should never have two elements on the same page with the same ID. If you need it for your CSS, use a class instead.
If you absolutely must select by duplicate ID, use an attribute selector:
$('[id="a"]');
Take a look at the fiddle: http://jsfiddle.net/P2j3f/2/
Note: if possible, you should qualify that selector with a type selector, like this:
$('span[id="a"]');
The reason for this is because a type selector is much more efficient than an attribute selector. If you qualify your attribute selector with a type selector, jQuery will first use the type selector to find the elements of that type, and then only run the attribute selector on those elements. This is simply much more efficient.
There should only be one element with a given id. If you're stuck with that situation, see the 2nd half of my answer for options.
How a browser behaves when you have multiple elements with the same id (illegal HTML) is not defined by specification. You could test all the browsers and find out how they behave, but it's unwise to use this configuration or rely on any particular behavior.
Use classes if you want multiple objects to have the same identifier.
<div>
<span class="a">1</span>
<span class="a">2</span>
<span>3</span>
</div>
$(function() {
var w = $("div");
console.log($(".a").length); // 2
console.log($("body .a").length); // 2
console.log($(".a", w).length); // 2
});
If you want to reliably look at elements with IDs that are the same because you can't fix the document, then you will have to do your own iteration as you cannot rely on any of the built in DOM functions.
You could do so like this:
function findMultiID(id) {
var results = [];
var children = $("div").get(0).children;
for (var i = 0; i < children.length; i++) {
if (children[i].id == id) {
results.push(children[i]);
}
}
return(results);
}
Or, using jQuery:
$("div *").filter(function() {return(this.id == "a");});
jQuery working example: http://jsfiddle.net/jfriend00/XY2tX/.
As to Why you get different results, that would have to do with the internal implementation of whatever piece of code was carrying out the actual selector operation. In jQuery, you could study the code to find out what any given version was doing, but since this is illegal HTML, there is no guarantee that it will stay the same over time. From what I've seen in jQuery, it first checks to see if the selector is a simple id like #a and if so, just used document.getElementById("a"). If the selector is more complex than that and querySelectorAll() exists, jQuery will often pass the selector off to the built in browser function which will have an implementation specific to that browser. If querySelectorAll() does not exist, then it will use the Sizzle selector engine to manually find the selector which will have it's own implementation. So, you can have at least three different implementations all in the same browser family depending upon the exact selector and how new the browser is. Then, individual browsers will all have their own querySelectorAll() implementations. If you want to reliably deal with this situation, you will probably have to use your own iteration code as I've illustrated above.
jQuery's id selector only returns one result. The descendant and multiple selectors in the second and third statements are designed to select multiple elements. It's similar to:
Statement 1
var length = document.getElementById('a').length;
...Yields one result.
Statement 2
var length = 0;
for (i=0; i<document.body.childNodes.length; i++) {
if (document.body.childNodes.item(i).id == 'a') {
length++;
}
}
...Yields two results.
Statement 3
var length = document.getElementById('a').length + document.getElementsByTagName('div').length;
...Also yields two results.
What we do to get the elements we need when we have a stupid page that has more than one element with same ID? If we use '#duplicatedId' we get the first element only. To achieve selecting the other elements you can do something like this:
$("[id=duplicatedId]")
You will get a collection with all elements with id=duplicatedId.
From the id Selector jQuery page:
Each id value must be used only once within a document. If more than one element has been assigned the same ID, queries that use that ID will only select the first matched element in the DOM. This behavior should not be relied on, however; a document with more than one element using the same ID is invalid.
Naughty Google. But they don't even close their <html> and <body> tags I hear. The question is though, why Misha's 2nd and 3rd queries return 2 and not 1 as well.
If you have multiple elements with same id or same name, just assign same class to those multiple elements and access them by index & perform your required operation.
<div>
<span id="a" class="demo">1</span>
<span id="a" class="demo">2</span>
<span>3</span>
</div>
JQ:
$($(".demo")[0]).val("First span");
$($(".demo")[1]).val("Second span");
Access individual item
<div id='a' data-options='{"url","www.google.com"}'>Google</div>
<div id='a' data-options='{"url","www.facebook.com"}'>Facebook</div>
<div id='a' data-options='{"url","www.twitter.com"}'>Twitter</div>
$( "div[id='a']" ).on('click', function() {
$(location).attr('href', $(this).data('options').url);
});
you can simply write $('span#a').length to get the length.
Here is the Solution for your code:
console.log($('span#a').length);
try JSfiddle:
https://jsfiddle.net/vickyfor2007/wcc0ab5g/2/

How to get all Child Elements with specific attribute in JavaScript

I have an object that was retrieved from this expression:
const element = document.querySelector("...my selector...");
I need to get all child elements that have certain attributes, The only way I know to get all children is by:
const children = Array.from(element.childNodes);
but now each child in children is not an element, rather a node, hence, I cannot use getAttribute('') on them;
How do I "cast" a Node to an Element?, Or is there a better way to do this?
How do I "cast" a Node to an Element?
You can't.
Elements are a subset of Nodes.
If it isn't an Element already, then you can't turn it into one.
Consider:
<div>Hello, <strong>World</strong></div>
You have two child nodes. The text node "Hello, " and the strong element node.
It doesn't make sense to treat "Hello, " as an element.
Consider using children instead of childNodes. It fetches only element children.
I need to get all child elements that have certain attributes
In that case, you're probably better off just using a selector which gets you that in the first place. You'll need a child combinator and an attribute selector in addition to your existing selector. Then you'll need to use All to get more than one result.:
document.querySelectorAll("...my selector... > [someAttribute]"
You said you want to select all children with a specific attribute. So select them with querySelectorAll using an attribute selector.
var elems = document.querySelectorAll("#theParentSelector > [theChildsAttribute]")
console.log(elems.length)
Array.from(elems).forEach( function (el) {
console.log(el.getAttribute("theChildsAttribute"))
});
<div id="theParentSelector">
<div theChildsAttribute="1">Foo 1</div>
<div>Bar</div>
<div theChildsAttribute="2">Foo 2</div>
<div theChildsAttribute="3">Foo 3</div>
</div>
You'd use children to gain access to all HTML based nodes:
document.querySelector("...my selector...").children

How is an event added to multiple elements with jquery selector? [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 6 years ago.
Improve this question
Suppose I have the following markup:
<ul>
<li class="one"></li>
<li class="one"></li>
<li class="one"></li>
</ul>
I add the folowing jquery to perfprm onclick function:
$(".one").click(function(){
var text = this.innerHTML;
alert(text);
})
The click function is assigned to all the three li elements. Usualy in javascript document.getElementsByClassName("one").myprop would return the first element with class name one's property myprop. Similarly if I add an event listener to document.getElementsByClassName("one") the event will be attached to the first element with class name one. E.g.
var one = document.getElementsByClassName("one");
one.addEventListener("click", function(){
alert();
});
will cause alert poup to appear when first li element is clicked.
Similarly jquery should assign the function to only the first element with class name one.
So how does jquery assign a function to all the elements with class name one?
So how does jquery assign a function to all the elements with class
name one?
The original selector is a collection of all ".one" elements passed to jQuery()
In the first formulation listed above, jQuery() — which can also be
written as $() — searches through the DOM for any elements
that match the provided selector and creates a new jQuery object that
references these elements:
$( "div.foo" );
emphasis added at "any".
.one() calls .on()
function one(types, selector, data, fn) {
return this.on(types, selector, data, fn, 1);
}
which calls .each() to attach event to each element at original selector $(".one")
return this.each(function () {
jQuery.event.add(this, types, fn, data, selector);
});
Taken from MDN,
Javascript 'getElementsByClassName' returns an array-like object of
all child elements which have all of the given class names.
So, you will not actually get only one result. Almost the same in your sample, $('.one') simply does the same.
And to get the first element of the returned elements, you can use;
var $jQueryObject = $('.one').eq(0);
//
var domElem = $('.one').get(0);
So, if you just want to assign the event handler to the first one, you can do;
$(".one").eq(0).click(function(){
var text = this.innerHTML;
alert(text);
})
The getElementsByClassName() method returns a collection of all elements in the document with the specified class name, as a NodeList object.
The NodeList object represents a collection of nodes. The nodes can be accessed by index numbers. The index starts at 0.te
get(0) will solve your problem.
The get() method gets the DOM elements specified by the selector.
For more info you can refer question.
Also see doc
$('.one').get(0).click(function()
{
var text = this.innerHTML;
alert(text);
})
As per your requirement "jquery should assign the function to only the first element with class name one" here is the code that can be of some help.
<script>
$(function(){
$("li.one:first-child").on("click",function(){
/*Your actions go here*/
var text =$(this).html();
alert(text);
});
});
In jQuery you may use :nth-child(0) or :eq(1) to point to first of class. Internally it gets all the elements as an list and then when you give (0) it means index=0. In case of :nth-child() index starts from 0 whereas in :eq() index starts with 1.
The JavaScript getElementsByClassName does the same thing.
Example :
$("button").click(function() {
//alert($(".one:nth-child(1)").text());
alert($(".one:eq(0)").text());
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<ul>
<li class="one">A</li>
<li class="one">B</li>
<li class="one">C</li>
</ul>
<button>Click</button>

About querySelector() with multiple selectors

I had a situation in which I wanted to focus either an input tag, if it existed, or it's container if it didn't. So I thought of an intelligent way of doing it:
document.querySelector('.container input, .container').focus();
Funny, though, querySelector always returns the .container element.
I started to investigate and came out that, no matter the order in which the different selectors are put, querySelector always returns the same element.
For example:
var elem1 = document.querySelector('p, div, pre');
var elem2 = document.querySelector('pre, div, p');
elem1 === elem2; // true
elem1.tagName; // "P".
My question is: What are the "reasons" of this behavior and what "rules" (if any) make P elements have priority over DIV and PRE elements.
Note: In the situation mentioned above, I came out with a less-elegant but functional solution:
(document.querySelector('.container input') ||
document.querySelector('.container') ).focus();
document.querySelector returns only the first element matched, starting from the first element in the markup. As written on MDN:
Returns the first element within the document (using depth-first
pre-order traversal of the document's nodes|by first element in
document markup and iterating through sequential nodes by order of
amount of child nodes) that matches the specified group of selectors.
If you want all elements to match the query, use document.querySelectorAll (docs), i.e. document.querySelectorAll('pre, div, p'). This returns an array of the matched elements.
The official document says that,
Returns the first element within the document (using depth-first pre-order traversal of the document's nodes|by first element in document markup and iterating through sequential nodes by order of amount of child nodes) that matches the specified group of selectors.
So that means, in your first case .container is the parent element so that it would be matched first and returned. And in your second case, the paragraph should be the first element in the document while comparing with the other pre and div. So it was returned.
That's precisely the intended behavior of .querySelector() — it finds all the elements in the document that match your query, and then returns the first one.
That's not "the first one you listed", it's "the first one in the document".
This works, essentially, like a CSS selector. The selectors p, div, pre and pre, div, p are identical; they both match three different types of element. So the reason elem1.tagName == 'P' is simply that you have a <p> on the page before any <pre> or <div> tags.
You can try selecting all elements with document.querySelectorAll("p.a, p.b") as shown in the example below and using a loop to focus on all elements that are found.
<html>
<body>
<p class="a">element 1</p>
<p class="b">element 2</p>
<script>
var list=document.querySelectorAll("p.a, p.b");
for (let i = 0; i < list.length; i++) {
list[i].style.backgroundColor = "red";
}
</script>
</body>
</html>

Categories