I have a requirement where I have to pickup the last .div within a container and apply some business logic to it. The selection of the last .div has to be dynamic because the user has the option to add/remove .div elements.
Initially I tried with querySelectorAll but it did not seem to work. So I decided to change it to getElementsByClassName and surprisingly it worked with the same logic. Can somebody please help me with the reason for why the remove_div doesn't work while the second one (remove_div_2) does?
Note: I am not looking for a fix/solution to the issue because I have already proceeded with the second option. I just want to know the reason why the option with querySelectorAll doesn't work.
Below is my code:
HTML:
<div id='container'>
<div id='div1' class='div'>This is Div 1</div>
<div id='div2' class='div'>This is Div 2</div>
<div id='div3' class='div'>This is Div 3</div>
</div>
<button type='button' id='append_div'>Append Div</button>
<button type='button' id='remove_div'>Remove Div</button>
<button type='button' id='remove_div_2'>Remove Div 2</button>
JavaScript:
window.onload = function () {
var elementToStyle = document.querySelectorAll("#container .div");
elementToStyle[elementToStyle.length - 1].classList.add('red');
document.getElementById('append_div').onclick = function () {
var divToInsert = document.createElement('div');
divToInsert.id = 'new_div';
divToInsert.className = 'div';
divToInsert.innerHTML = 'This is an appended div';
document.getElementById('container').appendChild(divToInsert);
var elToStyle = document.querySelectorAll("#container .div");
for (i = 0; i < elToStyle.length; i++)
elToStyle[i].classList.remove('red');
elToStyle[elToStyle.length - 1].classList.add('red');
};
document.getElementById('remove_div').onclick = function () {
var elToStyle = document.querySelectorAll("#container .div");
document.getElementById('container').removeChild(elToStyle[elToStyle.length - 1]);
elToStyle[elToStyle.length - 1].classList.add('red');
};
document.getElementById('remove_div_2').onclick = function () {
var elToStyle = document.getElementsByClassName('div');
document.getElementById('container').removeChild(elToStyle[elToStyle.length - 1]);
elToStyle[elToStyle.length - 1].classList.add('red');
};
}
The reason is because querySelectorAll method returns a static list. Any changes made to the document after the querySelectorAll is used (like removeChild in this case) will not be reflected in the list of nodes returned. Hence elToStyle[elToStyle.length - 1] would still point to the node that was removed.
Whereas, getElementsByClassName on the other hand returns a live list of nodes. This implies that elToStyle[elToStyle.length - 1] would always point to the last .div irrespective of any changes were done to the document after the node list was prepared or not.
Below is the extract from the official documentation available here
The NodeList object returned by the querySelectorAll() method must be
static, not live ([DOM-LEVEL-3-CORE], section 1.1.1). Subsequent
changes to the structure of the underlying document must not be
reflected in the NodeList object. This means that the object will
instead contain a list of matching Element nodes that were in the
document at the time the list was created.
Note: You can see this by doing a console.log(elToStyle); both before and after the removeChild.
If you want to reference the last division element just do the following...
var id = 'container';
var d = document.getElementById(id).getElementsByTagName('div')
var lastDiv = d[d.length - 1];
..and then apply your querySelector.
Related
I am trying to make a correct and incorrect question counter that shows groups of 4.
If I click on the first correct answer the counter works correctly and increases as I click, but it does not work with the second correct answer. The same happens with the wrong answers
This is the codes that I use, anyone could help me? Thx
HTML CODE:
¿Which of the following operations results in 8?
<input class="solucioncorrecta" value="6+2">
<input class="solucioncorrecta" value="7+1">
<input class="solucionincorrecta" value="1+1">
<input class="solucionincorrecta" value="2+2">
And the JS CODE:
<!-- CONTADOR FALLOS TEST -->
<script type="text/javascript">
var root = document.querySelector('.solucionincorrecta');
root.onclick = function() {
var elem = document.getElementById('contadorfallos');
elem.innerHTML = +elem.innerText + 1;
};
</script>
<!-- CONTADOR FALLOS TEST -->
<!-- CONTADOR ACIERTOS TEST -->
<script type="text/javascript">
var root = document.querySelector('.solucioncorrecta');
root.onclick = function() {
var elem = document.getElementById('contadoraciertos');
elem.innerHTML = +elem.innerText + 1;
};
</script>
The issue is that you are using document.querySelector() and not document.querySelectorAll()
document.querySelector() Returns the first match
document.querySelectorAll() Returns all matches
As a result, you are only setting an onclick property on the first .correcta and .incorrecta elements, not all of them.
To set this on all of them, you need to do two things:
You need use document.querySelectorAll() instead of document.querySelector(). This returns a list (specifically, a NodeList) of matching elements.
Loop over the items in your list, and attach onclick handlers to each of them. There are many ways to loop over a NodeList, listed here.
Here is an example:
// get all incorrect elements
var incorrectElements = document.querySelectorAll('.incorrecta');
// loop over each elements
for (var element of incorrectElements) {
// add an onclick
element.onclick = incorrectClickHandler
}
// this is the function being called by onclick
function incorrectClickHandler() {
score.innerText = parseInt(score.innerText) - 1;
}
It would be better if you upload your full codes. But anyway I write you some notes that probably answer your question.
-dont use the same name (root) for your .correcta and .incorrecta
-in your second <script>, you didnt defined button as an object . So browser cant understand it.
I have 2 divs: 1 on the left half of the page (A), one on the right (B). When hovering over a certain element of the right section, I want something to be displayed over the left one.
I did this using the following approach:
<div className="A">
<div className="hidden-div1">DIV 1</div>
<div className="hidden-div2">DIV 2</div>
<div className="hidden-div3">DIV 3</div>
</div>
<div className="B">
<div className="base-div1">
<h2 onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>Project 1</h2>
</div>
</div>
mouseOver(e){
const hiddenDiv1 = document.querySelector(".hidden-div1");
hiddenDiv1.style.display = "block";
}
mouseOut(e){
const hiddenDiv1 = document.querySelector(".hidden-div1");
hiddenDiv1.style.display = "none";
}
Problem is, considering I have 3 different hidden-divs and 3 different base-divs, I wanted to make 2 universal mouseOver and mouseOut functions for all of them. The way I tried it, is this:
mouseOver(e){
let hiddenDivName = "hidden-div" + e.target.className.slice(-1);
let hiddenDivSelector = document.getElementsByClassName(hiddenDivName);
hiddenDivSelector.style.display = "block";
}
but it returns "Cannot set property 'display' of undefined".
I tried console logging hiddenDivSelector and it shows an HTML collection and I don't know how to get my element. I've tried reading about it and visiting other questions but I couldn't apply anything to my situation
Event target returns a reference to DOM element. On DOM elements we can use getAttribute method and replace all non-digit characters by ''; result may be used to search DOM and iterate over returned array;
mouseOver(e){
let hiddenDivName = "hidden-div" + e.target.getAttribute('class').replace(/\D/g, '');
let hiddenDivSelector = document.getElementsByClassName(hiddenDivName);
Array.from( hiddenDivSelector ).forEach(el => el.style.display ) = "block";
}
I am trying to toggle a div when its name is clicked.
I have multiple coupls like that in my page, and I want it to work as
"when <p id= "d2"> is clicked => <div id="d2"> is toggled".
I tried those functions:
$(document).ready(function(){
$("p").click(function(){
$("div#" + $(this).attr('id')).toggle();
});
});
function rgt() {
//document.body.innerHTML = "";
var id = "d" + this.id;
var situation = document.getElementById(id).style.display;
if (situation == "none") {
situation = "block";
}
else {
situation = "none";
}
}
function showHide(theId) {
if (document.getElementById("d" + theId).style.display == "none") {
document.getElementById("d" + theId).style.display = "block";
}
else {
document.getElementById("d" + theId).style.display = "none";
}
}
I can't make it Work!!! Why is it?
the browser says:"no 'display' property for null"...
I will be more than happy to solve it with simple jquery
Ensure Your id Attributes Are Unique
Assuming that your id attributes are unique, which they are required to be per the specification:
The id attribute specifies its element's unique identifier (ID). The
value must be unique amongst all the IDs in the element's home subtree
and must contain at least one character. The value must not contain
any space characters.
You should consider renaming your id attributes to d{n} and your paragraphs to p{n} respectively as seen below :
<button id='p1'>p1</button> <button id='p2'>p2</button> <button id='p3'>p3</button>
<div id='d1'><pre>d1</pre></div>
<div id='d2'><pre>d2</pre></div>
<div id='d3'><pre>d3</pre></div>
which would allow you to use the following function to handle your toggle operations :
$(function(){
// When an ID that starts with P is clicked
$('[id^="p"]').click(function(){
// Get the proper number for it
var id = parseInt($(this).attr('id').replace(/\D/g,''));
// Now that you have the ID, use it to toggle the appropriate <div>
$('#d' + id).toggle();
})
});
Example Using Unique IDs
You can see an interactive example of this approach here and demonstrated below :
Consider Using data-* Attributes
HTML supports the use of data attributes that can be useful for targeting specific elements through jQuery and associating them to other actions. For instance, if you create an attribute on each of your "p" elements as follows :
<button data-toggles='d1'>p1</button>
<button data-toggles='d2'>p2</button>
<button data-toggles='d3'>p3</button>
and then simply change your jQuery to use those as selectors :
$(function(){
// When an element with a "toggles" attribute is clicked
$('[data-toggles]').click(function(){
// Then toggle its target
$('#' + $(this).data('toggles')).toggle();
});
});
Is this you are looking?
$("#p1").on("click", function() {
$("#d1").toggle();
});
js fiddle: https://jsfiddle.net/Jomet/09yehw9y/
jQuery(function($){
var $toggles = $('.divToggle');
var $togglables = $('.togglableDiv');
$toggles.on('click', function(){
//get the div at the same index as the p, and toggle it
$togglables.eq($toggles.index(this)).toggle();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<p class="divToggle">Show Me 1</p>
<p class="divToggle">Show Me 2</p>
<p class="divToggle">Show Me 3</p>
<div class="togglableDiv">Weeee 1</div>
<div class="togglableDiv">Weeee 2</div>
<div class="togglableDiv">Weeee 3</div>
Minimal approach using classes. This solution assumes the order of the p elements in the dom are in the same order as the divs are in the order. They do not have to be contiguous, but the order does matter with this solution.
ids are not the droids you are looking for.
An id needs to be unique. If you want to classify something one would suggest to use classes. You can actually use serveral of them for some fancy stuff. How about something like this:
<p class="toggle one">one</p>
<div class="toggle one" style="display:none">content one</div>
Straight forward. Every element that is a switch or switchable gets the class toggle. Each pair of switch and switchable(s) gets an additional identifier (like one, two, ...).
Simple JScript Implementation:
Now how about not using JQuery to work with that? Sure it i$ handy, but it hides all that neat stuff one would eventually like to learn her/himself!
var myToggle = {};
(function(module) {
"use strict";
(function init() {
var elements = document.getElementsByClassName("toggle");
var element;
var i = elements.length;
while (i) {
i -= 1;
element = elements[i].className;
elements[i].setAttribute("onclick", "myToggle.swap(\"" + element + "\")");
}
}());
module.swap = function(element) {
var couple = document.getElementsByClassName(element);
var i = couple.length;
while (i) {
i -= 1;
if (couple[i].style.display === "none" && couple[i].tagName === "DIV") {
couple[i].style.display = "block";
} else if (couple[i].tagName === "DIV") {
couple[i].style.display = "none";
}
}
};
}(myToggle));
<p class="toggle one">one</p>
<div class="toggle one" style="display:none">content one</div>
<p class="toggle two">two</p>
<div class="toggle two" style="display:none">content two 1</div>
<div class="toggle two" style="display:none">content two 2</div>
var myToggle = {} is the object we use to keep our little program contained. It prevents that our code conflicts with other declarations. Because what if some plugin on our site already declared a function called swap()? One would overwrite the other!
Using an object like this ensures that our version is now known as myToggle.swap()!
It may be hard to follow how it got to that name. Important hint: something looking like this... (function() { CODE } ()) ...is called an immediately-invoked function expression. iffy! It's a function that is immediatly executed and keeps its variables to itself. Or can give them to whatever you feed it in the last ()-pair.
Everything else is as verbose as can be... no fancy regular expressions, hacks or libraries. Get into it!
I would like to wrap the live content of a DOM element into another, keeping all the structure and all attached event listeners unchanged.
For example, I want this
<div id="original">
Some text <i class="icon></i>
</div>
to become
<div id="original">
<div id="wrapper">
Some text <i class="icon></i>
</div>
</div>
Preferably without jQuery.
If there is nothing else other than ID to distinguish your nodes, and given that #original has multiple child nodes, it would probably be simpler to create a new parent node and insert that:
var original = document.getElementById('original');
var parent = original.parentNode;
var wrapper = document.createElement('DIV');
parent.replaceChild(wrapper, original);
wrapper.appendChild(original);
and then move the IDs to the right place:
wrapper.id = original.id;
original.id = 'wrapper';
noting of course, that the variables original and wrapper now point at the 'wrong' elements.
EDIT oh, you wanted to leave the listeners attached... Technically, they still are, but they're now attached to the inner element, not the outer one.
EDIT 2 revised answer, leaving the event listeners attached to the original element (that's now the outer div):
var original = document.getElementById('original');
var wrapper = document.createElement('DIV');
wrapper.id = 'wrapper';
while (original.firstChild) {
wrapper.appendChild(original.firstChild);
}
original.appendChild(wrapper);
This works simply by successively moving each child node out of the original div into the new parent, and then moving that new parent back where the children were originally.
The disadvantage over the previous version of this answer is that you have to iterate over all of the children individually.
See https://jsfiddle.net/alnitak/d0jss2yu/ for demo
Alternatively, do it this way. It also displays result in the adjacent result div.
<div id="original">
Some text <i class="icon"></i>
</div>
<button onclick="myFunction()">do it</button>
<p type="text" id="result"></p>
<script>
function myFunction() {
var org = document.getElementById("original");
var i = org.innerHTML; //get i tag content
var wrap = document.createElement("div"); //create div
wrap.id="wrapper"; //set wrapper's id
wrap.innerHTML= i //set it to i tag's content
org.innerHTML=""; // clear #orignal first
org.appendChild(wrap); //append #wrapper and it's content
var result = org.outerHTML;
document.getElementById("result").innerText = result;
}
</script>
Updated answer:
This should work better and with less code than my previous answer.
var content = document.getElementById("myList").innerHTML;
document.getElementById("myList").innerHTML = "<div id='wrapper'></div>"
document.getElementById("wrapper").innerHTML = content;
EDIT: This will destroy any event listener attached to the child nodes.
Previous answer:
I don't tried it, but something like this should work:
var wrapper = document.createElement("DIV");
wrapper.id = "wrapper";
var content = document.getElementById("myList").childNodes;
document.getElementById("myList").appendChild(wrapper);
document.getElementById("wrapper").appendChild(content);
Create the wrapper element.
Get myList contents.
Add the wrapper element to myList.
Add myList contents to be child of the wrapper element.
I have a parent div and it has 9 same div's am trying to swap two div's index. Following is my code:
HTML:
<div id="cont" class="container">
<div class="no">1</div>
<div class="no">2</div>
<div class="no">3</div>
<div class="blank"></div>
<div class="no">4</div>
<div class="no">5</div>
<div class="no">6</div>
<div class="no">7</div>
<div class="no">8</div>
</div>
now I want to swap say 5th and 6th indexed elements. I have no clue how to do that in JavaScript. I know there is function called .index() but how to do that in pure JS.
Here's one implementation: http://jsfiddle.net/x8hWj/2/
function swap(idx1, idx2) {
var container = document.getElementById('cont');
// ditch text nodes and the like
var children = Array.prototype.filter.call(
container.childNodes,
function(node) {
return node.nodeType === 1;
}
);
// get references to the relevant children
var el1 = children[idx1];
var el2 = children[idx2];
var el2next = children[idx2 + 1];
// put the second element before the first
container.insertBefore(el2, el1);
// now put the first element where the second used to be
if (el2next) container.insertBefore(el1, el2next);
else container.appendChild(el1);
}
This starts by getting a list of all element child nodes, then uses insertBefore to rearrange them.