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)
};
Related
The elements are created in a for loop. I have tried storing each element ID in an array but it and using it but its always set to the last value the for loop ran through. Tried solving this problem with closures and still nothing works.
Heres a function I have after for loop, with i being passed in each time. moreinfolink is an empty array initialised outside the for loop. idarr is another array with different values in, which I want to reference using the moreinfolink array. Essentially when each element is clicked, the id it has based on its position in the moreinfolink should be used to then get its relevant position in the idarr, which are passed on to another page. And that pages content is genered using an API, for which we need the correct id (found in the idarr). Hope Im making sense.
function passthrough (a){
moreinfolink[a] = document.createElement("a");
moreinfolink[a].id = a;
newmoreinfo.appendChild(moreinfolink[a]); /* element I created elsewhere */
moreinfolink[a].innerHTML = "ID position in array is " + moreinfolink[a].id;
moreinfolink[a].href = "respage.html";
moreinfolink[a].onclick = moreinfo(idarr, moreinfolink[a].id); }
Both the overall array of returned ids (idarr) and each links relevant reference id (moreinfolink[a]) is passed into this function below upon the click event. Problem is that the last id is always passed through, so no matter which link you click it always passes through the last id the loop ended with instead of the one that should be assigned to that particular element.
function moreinfo (relarr, val) {
var carryover = [];
carryover.push(val);
window.name = carryover;
console.log("carryover is " + carryover)}
The function below is called when the other page is opened.
function generateapage () {console.log(window.name);}
You can add query stirng to href:
moreinfolink[a].href = `/respage&id=${moreinfolink[a].id}`;
Then you can read it when other pages load:
let id = new URLSearchParams(window.location.search).get('id')
I am trying to click a button, that will then change the display of overlay on my page to none. In the console it is telling me that, startButton.addEventListener is not a function. Can someone help me find the bug?
const letters = document.getElementById('qwerty');
const keyWords = document.getElementById('phrase');
const startButton = document.getElementsByClassName('btn__reset');
const overlay = document.getElementsByClassName('main-container');
var missed = 0;
startButton.addEventListener("click", function(){
overlay.style.display = 'none';
});
getElementsByClassName does not return a single element - it returns an arraylike HTMLCollection of all matching elements. You are not able to attach an event listener to a non-element.
https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
If you would like to just get the first element matching a class, you can use
const startButton = document.querySelector('.btn__reset'); (the dot signifies the selector is a class name)
Or access the first item of the return value of getElementsByClassName like so:
const startButton = document.getElementsByClassName.item(0); (You likely want to check for its existence first before)
https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection
As it currently stands your 'startButton' constant is actually returning a collection from this:
document.getElementsByClassName('btn__reset');
Even if it's a collection of only one object you need to go an additional level deeper to get the correct one
I suggest using
.getElementById('buttonID');
instead. This will will only return a single element to attach your listener to.
Get your button with getElementById, it works
let startButton = document.getElementById('button');
startButton.addEventListener("click", function(){
console.log('yes');
});
<button id="button">Go!</button>
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.
I am currently stuck on trying to use an onclick button to push a value into an array.
let display = document.getElementById('screen');
let results = [];
display.innerHTML = results[0];
$(document).ready(() => {
$('.one').click(function() {
results.push(1);
});
})
I am trying to push 1 into the array when the button is pushed, then display it. However, my current code does not push the function.
It does work, but the line that shows the results must be inside of the click callback. As it is now, the display gets updated just once, before the click happens.
Also, JQuery deprecated "shortcut" event methods (.click()) a while back and recommends the use of .on().
Lastly, innerHTML has performance and security implications, so don't use innerHTML when the string in question doesn't contain any HTML. Instead, use .textContent. But, because you are already using JQuery, you can use .text().
// If you are going to use JQuery, then use it.
// Here, we can get the element with an id of "screen"
// into a JQuery wrapped set object very easily.
// Naming the variable that will hold that JQuery object
// with a $ is a standard convention to remind you that
// the variable holds a JQuery object and not a standard
// DOM object.
let $display = $('#screen');
let results = [];
// And, with JQuery, if you just pass a function directly
// to JQuery, that function is automatically understood to
// be a document.ready callback
$(() => {
$('.one').on("click" ,function() {
results.push(1);
$display.text(results[0]); // This must be in the callback to show the most up to date information
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="button" class="one" value="push">
<div id="screen"></div>
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