I've got a task to do - to do list. I have a problem because i don't know how to move elements from parent "priority" to parent "standard".
My code of button to move is:
moveToStandardButton.addEventListener('click', function() {
const allListElements = document.querySelectorAll('[data-id="move"]');
for (let i = 0; i < allListElements.length; i++) {
let toClone = allListElements[i].checked;
}
})
toClone shoud have all list elements which are checked by user and then after click button it should move this elements to parent "standard". I've tried to do it like this but i can't use cloneNode at toClone or toClone[i].
If you log toClone, it is probably a boolean (true or false), as it is pulling whether the element is checked.
Try the following:
moveToStandardButton.addEventListener('click', function() {
// Get all elements that have a data-id of move
// I'm assuming these elements are the ones that need to move ;)
const allListElements = document.querySelectorAll('[data-id="move"]');
// iterate through the elements that need to move
for (let i = 0; i < allListElements.length; i++) {
let toClone = allListElements[i]; // Note that 'checked' isn't involved
if(!toClone.checked) continue; // If the element isn't checked, move to next element.
const clonedEl = toClone.cloneNode(true);
document.querySelector('#myOtherList').appendChild(clonedEl);
}
})
So after reading your question, I would recommend you watch or read some guides on JavaScript. You have some glaring issues that will become very apparent once you spend some time learning.
That said, I can lend some help to point you in the right direction.
1) It is not recommended to use data attributes as element identifiers. I would instead use a class, rather than data-id="move"
2) Your line let toClone is defined inside the for loop, and is scoped as such. It will not be available outside of the for loop.
3) Try creating an array outside of your for loop, and storing each checked element inside of it, and then operating on them after your loop.
Related
I'm building a law firm website in vanilla JS, that involves a lot of pages with content. I'm trying to have a single page display the different pages of content as the user clicks on different links.
I'm doing this by having the html already written with a default property of display: none, and as the user clicks on links, the target link can dynamically change that element's CSS to display: block, while making sure that all other elements are set to display: none, so that it can show one at a time. I have an array that contains all content elements and using splice to remove the target element from the map function which is supposed to set all elements to display: none, and them I'm setting the target element to display: block.
The problem is map is not executing when I use it within the event handler, but it does work when I use it outside the event handler. It's not throwing any errors, it's just not setting the display to none.
I've tried different ways of getting the same results such as using the forEach() function, but this is also having a similar result to map. It's not throwing any errors but it's not working. I tried creating a universal class with display: none, and dynamically adding that class to all target elements using map or forEach but that's also not working.
Also, splice was not working because it kept telling me that splice is not a function. After a lot of trial and error, and tons of research the only way I was able to fix this was by manually targeting each element using document.getElementById() which is extremely inefficient. It never worked when I used document.getElementsByClassName(), or when I used document.querySelectorAll()
Problem One: splice is not a function
Grabbing all elements and destructuring them for more efficiency
let contentText = document.getElementsByClassName('content-text');
let [immigrationText, contractsText, divorceText, debtCollectionText, escrowText, expungementText, mediationText, personalInjuryText, willsAndTrustsText] = contentText;
Using splice within the event handler
let immigrationEvent = () => {
contentText.splice(0, 1);
contentText.map((link) => link.style.display = 'none');
immigrationText.style.display = 'block';
};
Error
Uncaught TypeError: contentText.splice is not a function
at HTMLLIElement.immigrationEvent
Problem 2: Map feature doesn't execute
Grabbing all links and destructuring them. This is so the user can click on these links, and when they click on them, it should map through all the links that are not being called at the time, and should change the css display to none
let contentLinks = Array.from(document.getElementsByClassName('side-
nav-items'));
let [immigration, contracts, divorce, debtCollection, escrow,
expungement, mediation, personalInjury, willsAndTrusts] =
contentLinks;
Grabbing all elements that contain the content we wish to display, and destructuring them, and adding them to the array called contentText
let contentText = Array.from(document.getElementsByClassName('content-
text'));
let [immigrationText, contractsText, divorceText, debtCollectionText,
escrowText, expungementText, mediationText, personalInjuryText,
willsAndTrustsText] = contentText;
Splicing the array to exclude the page that's currently being called by the user, and then mapping the rest so that they switch the display property to none. This, of course, is the event handler function
let immigrationEvent = () => {
contentText.splice(0, 1);
contentText.map((link) => link.style.display = 'none');
immigrationText.style.display = 'block';
};
let contractsEvent = () => {
contentText.splice(1, 1);
contentText.map((link) => link.classList.add('content-hide'));
contractsText.style.display = 'block';
};
Calling the Event handler function. I'm including 2 examples
immigration.addEventListener('click', immigrationEvent, false);
contracts.addEventListener('click', contractsEvent, false);
Problem 1: Expected result, is I want splice to work when I use some type of select all feature, but it only works when I target them using
getElementById()
Problem 2: I want the map feature to set all target elements within the array to set to display none. Instead what happens is, as I click through the links, it adds the elements to the same page but doesn't hide the previous elements
This results in a "TypeError: contentText.splice is not a function":
let contentText = document.getElementsByClassName('content-text');
contentText.splice(0, 1);
...because getElementsByClassName returns "an array-like object" (a "live" HTMLCollection, which changes to track with the DOM), not a static array.
To make a copy in a real Array object, use Array.from(document.getElementsByClassName(...)).
For #2 the problem seems to be outside of the code you posted. Please post the code that reproduces the issue. Note that using .map, while valid, is not recommended if you just want to do something for each element of a collection.
let immigrationEvent = () => {
const elts = Array.from(document.getElementsByClassName("hideme"));
const [visibleElt] = elts.splice(0, 1);
elts.map((elt) => elt.style.display = 'none');
visibleElt.style.display = 'block';
}
document.querySelector("button").addEventListener('click', immigrationEvent, false);
<div class="hideme">one</div>
<div class="hideme">two</div>
<div class="hideme">three</div>
<button>Run</button>
I finally figured it out
Problem one: Nickolay was spot on. I needed to create an instance of my original array in order for JS to be able to parse through it
let contentText =
Array.from(document.getElementsByClassName('content-
text'));
Array from allowed me to use map, and splice etc.
Problem Two: Thanks to Nickolay I was able to notice that my map feature was working, but every time the splice was running, it was removing elements, and the page was not able to read those changes. Instead I used slice, and targeted the sliced item to be the one that would display the data, and the original array would remain intact, and the map feature would remove the display from all elements in the array. See code below
Destructuring the links that are going to trigger the event
let contentLinks =
Array.from(document.getElementsByClassName('side-nav-
items'));
let [immigration, contracts, divorce, debtCollection,
escrow, expungement, mediation, personalInjury,
willsAndTrusts] = contentLinks;
Destructuring the elements that are going to be displayed on the page
let contentText =
Array.from(document.getElementsByClassName('content-
text'));
let [immigrationText, contractsText, divorceText,
debtCollectionText, escrowText, expungementText,
mediationText, personalInjuryText, willsAndTrustsText] =
contentText;
One of the events that are supposed to display only the target content. We slice the target code block and have it display block while having all elements display none
let immigrationEvent = () => {
let targetElement = contentText.slice(0, 1);
contentText.map((text) => text.style.display = 'none');
targetElement[0].style.display = 'block';
console.log(targetElement)
};
In one of my projects I just discovered, that sometimes iterating over an array of html elements (and change all of them) just affects the last element. When I log the element's attributes I can see that the loop definitily adresses every element but nevertheless visibly just the last element is getting changed.
Can anyone explain me why?
I already figured out, that a solution is to use createElement() and appendChild() instead of insertHTML. I just want to understand why javascript behaves like this.
Here is my example code:
/* creating 5 elements and storing them into an array */
var elementArray = [];
for(var n = 0;n<5;n++)
{
document.body.innerHTML += "<div id='elmt_"+n+"'>"+n+"</div>\n";
elementArray[n] = document.getElementById("elmt_"+n);
}
/* loop over these 5 elements */
for(var n = 0;n<5;n++)
{
console.log(elementArray[n].id); // logs: elmt_0 elmt_1 elmt_2 elmt_3 elmt_4
elementArray[n].innerHTML = "test"; // changes just the last element (elmt_4) to "test"
}
I created an example here: http://jsfiddle.net/qwe44m1o/1/
1 - Using console.log(elementArray[n]); in your second loop shows that innerHTML in this loop is modifying html inside your array, not in your document. That means that you are storing the div element in your array, not a shortcut to document.getElementById("elmt_"+n)
See the JSFiddle
2 - If you want to store a shortcut in order to target an element by ID, you have to add quotes for elementArray[n] = "document.getElementById('elmt_"+n+"')";, and use it with eval like this : eval(elementArray[n]).innerHTML = n+"-test";
See the JSFiddle for this try
First question ever, new to programming. I'll try to be as concise as possible.
What I want to do is to create a bunch of children inside a selected div and give each of them specific html content (from a predefined array) and a different id to each child.
I created this loop for the effect:
Game.showOptions = function() {
var i = 0;
Game.choiceElement.html("");
for (i=0; i<Game.event[Game.state].options.length; i++) {
Game.choiceElement.append(Game.event[Game.state].options[i].response);
Game.choiceElement.children()[i].attr("id","choice1");
}
};
Using the predefined values of an array:
Game.event[0] = { text: "Hello, welcome.",
options: [{response: "<a><p>1. Um, hello...</p></a>"},
{response: "<a><p>2. How are you?</p></a>"}]
};
This method does not seem to be working, because the loop stops running after only one iteration. I sincerely have no idea why. If there is a completely different way of getting what I need, I'm all ears.
If I define the id attribute of each individual p inside the array, it works, but I want to avoid that.
The idea is creating a fully functional algorithm for dialogue choices (text-based rpg style) that would work with a predefined array.
Thanks in advance.
The problem with your loop as I see it could be in a couple different places. Here are three things you should check for, and that I am assuming you have but just didn't show us...
Is Game defined as an object?
var Game = {};
Is event defined as an array?
Game.event = new Array();
Is Game.state returning a number, and the appropriate number at that? I imagine this would be a little more dynamic then I have written here, but hopefully you'll get the idea.
Game.state = 0;
Now assuming all of the above is working properly...
Use eq(i) instead of [i].
for (var i = 0; i<Game.event[Game.state].options.length; i++) {
Game.choiceElement.append(Game.event[Game.state].options[i].response);
Game.choiceElement.children().eq(i).attr("id","choice" + (i + 1));
}
Here is the JSFiddle.
I swear this was just working fine a few days ago...
elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
while (elm[0]){
frag.appendChild(elm[0]);
}
Right, so, this should append each node from our elm node list. When the first one is appended, the second "moves" to the first position in node list, hence the next one is always elm[0]. It should stop when the elm nodeList is completely appended. However, this is giving me an infinite loop. Thoughts?
EDIT - because I've gotten the same answer several times...
A nodeList is not an array, it is a live reference. When a node is "moved" (here, appended) it should be removed automatically from the node list. The answers all saying "you're appending the same element over and over" - this is what's happening, it shouldn't be. A for loop shouldn't work, because when the first node is appended, the next node takes its index.
2nd EDIT
So the question is now "why is the nodeList behaving as an array?". The node list SHOULD be updating every time a node is being appended somewhere. Most peculiar.
Solution (in case someone needs something to handle live + non-live node lists)
elm = (/*however you're getting a node list*/);
var frag = document.createDocumentFragment();
var elength = elm.length;
for (var b = 0; b<elength; b++){
if (elm.length === elength){
frag.appendChild(elm[b]);
} else {
frag.appendChild(elm[0].cloneNode());
}
}
Basically, just checking to see if the node list has changed length.
From the MDN Docs
Element.querySelectorAll
Summary
Returns a non-live NodeList of all elements descended from the element on which it is invoked that match the specified group of CSS selectors.
Syntax
elementList = baseElement.querySelectorAll(selectors);
where
elementList is a non-live list of element objects.
baseElement is an element object.
selectors is a group of selectors to match on.
From the docs above you can see it does not automatically remove it when you append it to another element since it is non live. Run a demo to show that feature.
var selector = "div";
elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
console.log("before",elm.length);
frag.appendChild(elm[0]);
console.log("after",elm.length);
When the code above runs, in the console you get.
before 3
after 3
If you want to do the while loop, convert to an array and shift() the items off
var selector = "div";
var elmNodeLIst = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
var elems = Array.prototype.slice.call(elmNodeLIst );
while (elems.length) {
frag.appendChild(elems.shift());
}
console.log(frag);
You are appending the first item in the node list, over and over and over. You never removing any items from the array, but always adding the first one to the fragment. And the first one is always the same.
elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
while (elm.length){
frag.appendChild(elm.shift());
}
This may be closer to what you meant to do. We can use while (elm.length) because as items get removed form the array, eventually length will be zero which is a flasy value and the loop will stop.
And we use elm.shift() to fetch the item from the array because that method will return the item at index zero and remove it from the array, which gives us the mutation of the original array we need.
I think you thought this might work because a node can only have one parent. Meaning adding somewhere removes it from the previous parent. However, elm is not a DOM fragment. It's just a aray (or perhaps a NodeList) that holds references to element. The array is not the parent node of these elements, it just holds references.
Your loop might work if you had it like this, since you are query the parent node each time for its children, a list of node that will actually change as you move around:
elm = document.getElementById(id);
var frag = document.createDocumentFragment();
while (elm.children[0]){
frag.appendChild(elm.children[0]);
}
I wouldn't have expected it to work in the first place.
Your elm array is initialized, and never updated. Even if the result from running document.querySelectorAll(selector); would return something different, this doesn't change your current references in the array.
You would either need to rerun the selector, or manually remove the first element in the array after appending it.
elm[0] is static and unchanging in above code
fix is as below
elm = document.querySelectorAll(".container");
var frag = document.createDocumentFragment();
console.log(elm);
var i=0;
while (elm[i]){
frag.appendChild(elm[i++]);
}
I didn't actually focus much on the code (and if it made sense -judging from the comments- or not); but if this worked a few days ago then the problem is in the input you are giving to your code selector.
That's when Unit Testing comes in handy. If you can remember the input with which the code worked, then you can make it work again and start debugging from there.
Otherwise, you are just lying to yourself.
It's an infinite loop as it's written right now because elm[0] always refers to the same element, and that element is never null (any non-null/non-zero result would be true). You also don't do anything with the elements themselves to make it iterate across the list. You should be using a for loop instead of a while or at least having some kind of indexer to try to traverse the collection.
elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
for (i= 0; i < elm.length; i++)
{
frag.appendChild(elm[i]);
}
Edit:
From the documentation:
A "live" collection
In most cases, the NodeList is a live collection. This means that changes on the DOM tree >are going to be reflected on the collection.
var links = document.getElementsByTagName('a'); // links.length === 2 for instance
document.body.appendChild( links[0].cloneNode(true) ); // another link is added to the document
// the 'links' NodeList is automatically updated
// links.length === 3 now. If the NodeList is the return value of document.querySelectorAll, it is NOT live.
Going on this documentation, your current usage of the method indicates you do not have a live NodeList. Thus appending will never modify the original list. You will either need to modify your usage within the loop to mirror this usage of .cloneNode(true) or iterate manually.
This is very basic I'm sure to JavaScript but I am having a hard time so any help would be appreciated.
I want to call a function within a for loop using the mouseDown event occurring within an object's second child node. The part italicized is my attempt to do this. The swapFE function is still a work in progress by the way. And one more thing is when I put the italicized part in the swapFE function everything works properly but when I put it in the for loop it doesn't all show up. I don't know why. I am basically trying to swap French phrases for English ones when I click the phrase with my mouse.
function setUpTranslation() {
var phrases = document.getElementsByTagName("p");
var swapFE = document.getElementsByTagName("phrase");
for (i = 0; i<phrases.length; i++) {
phrases[i].number = i;
phrases[i].childNodes[1].innerHTML = french[i];
*phrases[i].childNodes[1].onMouseDown = swapFE;*
}
}
/* see "function_swapFE(phrase,phrasenum);" below. The expression to call function swapFE
is located underneath "function swapFE(e)" because although the directions said to put the
"run swapFE" within the for loop it did not work properly that's why I put it beneath the
"function swapFE(e)".*/
function swapFE(e) {
var phrase = eventSource(e);
var phasenum = parseInt(1) = [1].innercontent.previousSibling;
phrase.node.previousSibling.onmousedown=swapFE
function_swapFE(e)(phrase,phrasenum);
}
}
If you have questions let me know.
Thanks for your help.
With this, you are creating a local variable named swapFE;
var swapFE =
document.getElementsByTagName("phrase");
Then with this you are setting this var as a mouseDown
phrases[i].childNodes[1].onMouseDown =
swapFE;*
That's not right... onMouseDown should be set to a function name, not a local variable of that name. So you should probably rename the local var to something else. That will at least get you closer to a solution.
I can only make a couple of guesses at what might be failing with your source code. Firstly, the following code assumes that all <p> tags have at least 2 child elements:
for (i = 0; i<phrases.length; i++) {
phrases[i].number = i;
phrases[i].childNodes[1].innerHTML = french[i];
*phrases[i].childNodes[1].onMouseDown = swapFE;*
}
If any <p> tags on your page have less than 2 child elements, an error will be thrown and script execution will halt. The best solution for this would be to add a class attribute to each <p> tag that will contain the elements you're looking for. Alternatively, you could just check for the existence of the second childnode with an if statement. Or you could do both.
Secondly, like all events, onmousedown should be declared in lowercase. Setting onMouseDown will not throw an error, but instead create a custom property on the element instead of attaching an event handler.
Finally, the following code:
var swapFE = document.getElementsByTagName("phrase");
will locally override the global function swapFE for that function, replacing it with a variable instead.
This is how I might write your setupTranslation function:
function setUpTranslation() {
var phrases = document.getElementsByTagName("p");
// rename the swapFE var as outlined below
var swapFENodes = document.getElementsByTagName("phrase");
var cNode; // set up an empty variable that we use inside the loop
for (i = 0; i<phrases.length; i++) {
/* Check for the existence of the translationPhrase class
in the <p> tag and the set the cNode var to childNodes[1]
and testing for its existence at the same time */
if (cNode.className != "translationPhrase"
|| !(cNode = phrases[i].childNodes[1]))
continue; // skip to the next iteration
phrases[i].number = i;
cNode.innerHTML = french[i];
cNode.onmousedown = swapFE; // Changed onMouseDown to onmousedown
}
}