Here is the case. I have a page, on which I have several options to choose. Choice is being made by clicking a link, which leads to an anchor and displays necessary content. Unfortunately this could not be changed. The problem is, that I also have a language panel, which leads me to the same page but already translated to necessary locale. My goal is to be able change language, but also save the chosen anchor from a clicked link. Was not able to find solution yet, could someone please give any idea, how that could be done?
Here is link to a fiddle https://jsfiddle.net/william_eduards/psuLrv02/87/
let contents = Array.from(document.querySelectorAll('.content'));
let contentOne = document.querySelector('.content--a');
let contentTwo = document.querySelector('.content--b');
const handleClick = (e) => {
e.preventDefault();
contents.forEach(node => {
node.classList.remove('active');
});
e.currentTarget.classList.add('active');
if (document.querySelector('.content-a').classList.contains('active')) {
contentOne.classList.add('show');
} else {
contentOne.classList.remove('show');
}
if (document.querySelector('.content-b').classList.contains('active')) {
contentTwo.classList.add('show');
} else {
contentTwo.classList.remove('show');
}
}
contents.forEach(node => {
node.addEventListener('click', handleClick);
});
.content--a, .content--b {
display: none;
}
.show {
display: block;
}
<div class="container">
<div class="select-option">
<a class="content content-a" href="#contentA">Content A</a>
<a class="content content-b" href="#contentB">Content b</a>
</div>
<div class="options">
<div class="content--a" id="contentA">
<p>I am content a</p>
</div>
<div class="content--b" id="contentB">
<p>I am content b</p>
</div>
</div>
</div>
To show your contents, just use CSS with pseudo class :target (remove all your js)
.content--a, .content--b {
display: none;
}
.options > *:target {
display: block;
}
If your page is reloaded when you select language, use sessionStorage to remember which link was last clicked, and restore it on next page load. Following code assumes the URL is unchanged when you switch languages:
window.addEventListener('DOMContentLoaded', e => {
const contents = document.querySelectorAll('.content');
//Save selection on click as url#anchor
contents.forEach(node => {
node.addEventListener('click', e => {
const href = node.getAttribute('href');
console.log(href);
if (href) {
const spl = href.split('#');
if (spl.length > 1 && spl[1].length) {
const spl2 = window.location.href.split('#');
sessionStorage.setItem('shown-content', spl2[0] + '#' + spl[1]);
console.log(sessionStorage.getItem('shown-content'));
}
}
});
});
//Restore selection on load if URL is the same up until anchor
const anch = sessionStorage.getItem('shown-content');
if (anch) {
const spl = anch.split('#');
if (spl.length) {
const spl2 = window.location.href.split('#');
console.log(spl[0], spl2[0]);
if (spl[0] == spl2[0])
window.location.href = anch;
}
}
});
Adapt above code to your needs if URL changes between languages
Full example (doesn't run as a snippet. Use server or local html file)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Test</title>
<style>
.content--a, .content--b {
display: none;
}
.options > *:target {
display: block;
}
</style>
<script>
window.addEventListener('DOMContentLoaded', e => {
const contents = document.querySelectorAll('.content');
//Save selection on click as url#anchor
contents.forEach(node => {
node.addEventListener('click', e => {
const href = node.getAttribute('href');
console.log(href);
if (href) {
const spl = href.split('#');
if (spl.length > 1 && spl[1].length) {
const spl2 = window.location.href.split('#');
sessionStorage.setItem('shown-content', spl2[0] + '#' + spl[1]);
console.log(sessionStorage.getItem('shown-content'));
}
}
});
});
//Restore selection on load if URL is the same up until anchor
const anch = sessionStorage.getItem('shown-content');
if (anch) {
const spl = anch.split('#');
if (spl.length) {
const spl2 = window.location.href.split('#');
console.log(spl[0], spl2[0]);
if (spl[0] == spl2[0])
window.location.href = anch;
}
}
});
</script>
</head>
<body>
<div class="container">
<div class="languages">
English
Français
</div>
<div class="select-option">
<a class="content content-a" href="#contentA">Content A</a>
<a class="content content-b" href="#contentB">Content b</a>
</div>
<div class="options">
<div class="content--a" id="contentA">
<p>I am content a</p>
</div>
<div class="content--b" id="contentB">
<p>I am content b</p>
</div>
</div>
</div>
</body>
</html>
You can simply save the element which is active. No big deal.
let contents = Array.from(document.querySelectorAll('.content'));
...
let activeEl = null;
const handleClick = (e) => {
...
e.currentTarget.classList.add('active');
activeEl = e.currentTarget; // adding the `<a/>` tag in a global
...
}
Related
This js code is supposed to change the the value of class attribute 'skills__open' to 'skills__close' when the 'skills__header' div is clicked. It changes the 'skills__open' to 'skills__close' on the first click, but after the first click, it is not working.
Js
const skillsContent = document.getElementsByClassName('skills__content'),
skillsHeader = document.querySelectorAll('.skills__header')
function toggleSkills(){
let itemClass = this.parentNode.ClassName
for(i=0; i < skillsContent.length; i++){
skillsContent[i].className = 'skills__content skills__close'
}
if(itemClass === 'skills__content skills__close'){
this.parentNode.className = 'skills__content skills__open'
}
}
skillsHeader.forEach((el) =>{
el.addEventListener('click', toggleSkills)
});
Html
<div class="skills__content skills__open">
<div class="skills__header">
</div>
<div class="skills__content skills__close">
<div class="skills__header">
</div>
<div class="skills__content skills__close">
<div class="skills__header">
</div>
css
.skills__open .skills__list{
height: max-content;
margin-bottom: var(--mb-2-5);
}
.skills__open .skills__arrow{
transform: rotate(-180deg);
}
nothing significant
try that:
const skillsContents = document.querySelectorAll('.skills__content');
skillsContents.forEach( skc =>
{
skc
.querySelector('.skills__header')
.addEventListener('click', () =>
{
skillsContents.forEach( el =>
{
if (skc===el) el.classList.replace('skills__close','skills__open')
else el.classList.replace('skills__open','skills__close')
})
})
});
So I am working with a DOM challenge to improve my skills. I have reached the deliverable but would like to additionally only have 1 instance of each logged number run with the count increasing in that single string.
With what I have so far below, for each time my heart button is clicked during whichever count is present it will record and append that count as an li to my ul.likes.
How would I post only one li for each individual count and update the button click count within said specific li?
JS
const counter = document.querySelector("#counter");
const likes = document.querySelector(".likes");
function startCounter() {
setInterval(() => {
counter.textContent++;
}, 1000);
}
const minus = document.querySelector("#minus");
minus.addEventListener("click", () => {
counter.textContent--;
});
const plus = document.querySelector("#plus");
plus.addEventListener("click", () => {
counter.textContent++;
});
let lookup = {};
function buttonClicked() {
let li = document.createElement("li");
let activeCount = counter.textContent;
let likeList = document.querySelector("li");
if (lookup[activeCount]) {
lookup[activeCount]++;
} else lookup[activeCount] = 1;
li.innerHTML = `${activeCount} has been liked ${lookup[activeCount]} times!`;
likes.appendChild(li);
console.log(likes);
}
const heart = document.querySelector("#heart");
heart.addEventListener("click", () => {
buttonClicked();
});
const pause = document.querySelector("#pause");
startCounter();
HTML
<html>
<head>
<meta charset="UTF-8">
<title>Dom Challenge</title>
<style>
#counter {
background-color: black;
color: white;
display: table;
padding: 10px;
}
</style>
</head>
<body>
<h1>
Welcome to the DOM Challenge
</h1>
<h1 id='counter'>
0
</h1>
<button id='minus'> ➖ </button>
<button id='plus'> ➕ </button>
<button id='heart'> ❤️ </button>
<button id='pause'> pause </button>
<ul class='likes'></ul>
<div>
<h3>Comments</h3>
<div id='list' class='comments'></div>
<h3>Leave a comment</h3>
<form id="comment-form" action="">
<input type='text' name="comment" id="comment-input" cols="30" rows="10">
</br>
<button id='submit'>submit</button>
</form>
</div>
<!--<script src="js/index.min.js"></script>-->
<script src="js/challenge.js"></script>
</body>
</html>
function buttonClicked() {
// Create an array of all the current li items
const like_list = likes.getElementsByTagName('li');
let activeCount = counter.textContent;
// incremet by default
if (lookup[activeCount]) {
lookup[activeCount]++;
} else lookup[activeCount] = 1;
//search each li element and if the id of that element matched the activeCount update its innerHTML
for(var i =0;i<like_list.length;i++){
if(like_list[i].id == activeCount){
like_list[i].innerHTML = `${activeCount} has been liked ${lookup[activeCount]} times!`;
return;
}
}
let li = document.createElement("li");
li.innerHTML = `${activeCount} has been liked ${lookup[activeCount]} times!`;
//set the id attribute of new li elements
li.setAttribute("id", activeCount);
likes.appendChild(li);
}
This question already has answers here:
Why does jQuery or a DOM method such as getElementById not find the element?
(6 answers)
Closed 1 year ago.
My Issue:
Please help me run this code as it should. I am getting a null form error when typing a City name in the place holder and I'm not sure why I am practicing this code from here: https://webdesign.tutsplus.com/tutorials/build-a-simple-weather-app-with-vanilla-javascript--cms-33893
/*SEARCH BY USING A CITY NAME (e.g. athens) OR A COMMA-SEPARATED CITY NAME ALONG WITH THE COUNTRY CODE (e.g. athens,gr)*/
const form = document.querySelector(".top-banner form");
const input = document.querySelector(".top-banner input");
const msg = document.querySelector(".top-banner .msg");
const list = document.querySelector(".ajax-section .cities");
/*SUBSCRIBE HERE FOR API KEY: https://home.openweathermap.org/users/sign_up*/
const apiKey = "f077e7d6167270fa866a36699ab528fe"; /*REPLACE THIS WITH YOUR API KEY FROM OPENWEATHERMAP.ORG*/
form.addEventListener("submit", e => {
e.preventDefault();
let inputVal = input.value;
//check if there's already a city
const listItems = list.querySelectorAll(".ajax-section .city");
const listItemsArray = Array.from(listItems);
if (listItemsArray.length > 0) {
const filteredArray = listItemsArray.filter(el => {
let content = "";
//athens,gr
if (inputVal.includes(",")) {
//athens,grrrrrr->invalid country code, so we keep only the first part of inputVal
if (inputVal.split(",")[1].length > 2) {
inputVal = inputVal.split(",")[0];
content = el
.querySelector(".city-name span")
.textContent.toLowerCase();
} else {
content = el.querySelector(".city-name").dataset.name.toLowerCase();
}
} else {
//athens
content = el.querySelector(".city-name span").textContent.toLowerCase();
}
return content == inputVal.toLowerCase();
});
if (filteredArray.length > 0) {
msg.textContent = `You already know the weather for ${
filteredArray[0].querySelector(".city-name span").textContent
} ...otherwise be more specific by providing the country code as well 😉`;
form.reset();
input.focus();
return;
}
}
//ajax here
const url = `https://api.openweathermap.org/data/2.5/weather?q=${inputVal}&appid=${apiKey}&units=metric`;
fetch(url)
.then(response => response.json())
.then(data => {
const {
main,
name,
sys,
weather
} = data;
const icon = `https://s3-us-west-2.amazonaws.com/s.cdpn.io/162656/${
weather[0]["icon"]
}.svg`;
const li = document.createElement("li");
li.classList.add("city");
const markup = `
<h2 class="city-name" data-name="${name},${sys.country}">
<span>${name}</span>
<sup>${sys.country}</sup>
</h2>
<div class="city-temp">${Math.round(main.temp)}<sup>°C</sup></div>
<figure>
<img class="city-icon" src="${icon}" alt="${
weather[0]["description"]
}">
<figcaption>${weather[0]["description"]}</figcaption>
</figure>
`;
li.innerHTML = markup;
list.appendChild(li);
})
.catch(() => {
msg.textContent = "Please search for a valid city 😩";
});
msg.textContent = "";
form.reset();
input.focus();
});
<!DOCTYPE html>
<html>
<head>
<script src="main.js"></script>
</head>
<body>
<div class="api">
<div class="container">🌞 This demo needs an OpenWeather API key to work. <a target="_blank" href="https://home.openweathermap.org/users/sign_up">Get yours here for free!</a>
</div>
</div>
<section class="top-banner">
<div class="container">
<h1 class="heading">Simple Weather App</h1>
<form>
<input type="text" placeholder="Search for a city" autofocus>
<button type="submit">SUBMIT</button>
<span class="msg"></span>
</form>
</div>
</section>
<section class="ajax-section">
<div class="container">
<ul class="cities"></ul>
</div>
</section>
<footer class="page-footer">
<div class="container">
</div>
<small>Made with <span>❤</span> by George Martsoukos
</small>
<li class="city">
<h2 class="city-name" data-name="...">
<span>...</span>
<sup>...</sup>
</h2>
<span class="city-temp">...<sup>°C</sup></span>
<figure>
<img class="city-icon" src="..." alt="...">
<figcaption>...</figcaption>
</figure>
</li>
</footer>
</body>
</html>
It's because your javascript code is executed before DOM is fully loaded.
So you have two choices, either move
<script src="main.js"></script> as the last item inside body (before </body>)
or place all your javascript code inside:
document.addEventListener("DOMContentLoaded", e =>
{
// your code here
});
I know why it shows error because due to asynchronous, button is not loaded yet. Is there any way to fix it? I cannot explain much in words. At the time window is loaded , there is no button because it appears only after clicking id. i tried to put all codes but stack overflow requires much explanation
This is my main js code. Fetch works and i didnot include all codes.
const controlMovie = async()=> {
const id = window.location.hash.replace('#', '');
if(id){
// new id
clearMovie();
state.control = new Control(id);
await state.control.getMovies();
UImovie(state.control);
}
return id;
};
const viewList = async()=>
{
const id = window.location.hash.replace('#', '');
state.view= new Control(id);
await state.view.getMovies();
UIlist(state.view);
}
['hashchange', 'load'].forEach(event => window.addEventListener(event, controlMovie));
document.querySelector('.add').addEventListener('click', viewList);
This is for UI js part
const UImovie = (info)=> {
const markup = `
<div class="img-fig">
<img src="${info.image}" alt="${info.title}" class="movie-img">
</div>
<div class="movie_details">
<br>
<h3 style="align-items: center; font-weight: bold;"> ${info.title}</h3>
<p>Writer: ${info.writer}</p>
<p>Released date: ${info.year}</p>
<p>Actors: ${info.actors} </p>
<p>imdbRating: ${info.rating}</p>
<p>Total seasons: ${info.seasons}</p>
<p style="font-style: italic; color: red; font-size: 16px;"> "${info.story}"</p>
<button class= "add">+ Add to watchlist</button>
</div>
</div>
`;
document.querySelector('.movies-result').insertAdjacentHTML('afterbegin', markup);
};
const UIlist = (UI)=> {
const markup = `
<h3> ${UI.title} <button class="icons"><ion-icon name="trash"></ion-icon></button></h3>
`;
document.querySelector('.lists').insertAdjacentHTML('afterbegin', markup);
}
As commented, based on the provided code, you are added .icons dynamically. but .addEventListener is being executed during pageload. Due to this, when its executed, there is no elements available on DOM and no listener is added.
You should try using HTMLElement objects instead:
const UIlist = (UI)=> {
const h3 = document.createElement('h3');
h3.innerText = UI.title;
const button = document.createElement('button');
const icon = document.createElement('ion-icon');
icon.setAttribute('name', 'trash');
button.append(icon);
button.classList.add('icons');
button.addEventListener('click', function() {
console.log('Button Clicked');
})
h3.append(button)
document.querySelector('.lists').insertAdjacentElement('afterbegin', h3);
}
UIlist( { title: 'Bla' } )
<div>
<div class='lists'></div>
</div>
Here is a sample of index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Students</title>
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/design.css">
</head>
<body>
<div class="page">
<div class="page-header cf">
<h2>Students</h2>
<!-- dynamically insert search form here (optional) -->
</div>
<ul class="student-list">
<li class="student-item cf">
<div class="student-details">
<img class="avatar" src="https://randomuser.me/api/portraits/thumb/women/67.jpg">
<h3>iboya vat</h3>
<span class="email">iboya.vat#example.com</span>
</div>
<div class="joined-details">
<span class="date">Joined 07/15/15</span>
</div>
</li>
</ul>
Here is App.js
const studentList=document.querySelector('.student-list');
const page=document.querySelector(".page");
//constants used in displayPage function
//listItems ===54
const listItems=document.querySelector(".student-list").children;
const numberOfItems=10;
displayPage=(list,pageNumber)=> {
const SI=(pageNumber*numberOfItems)-numberOfItems;
const EI=pageNumber*numberOfItems;
Array.from(list).forEach((item,index)=> {
if (index>= SI && index<EI) {
item.style.display="block";
} else {
item.style.display="none";
}
})
}
//number of pages to display
addPaginationLinks=(list)=> {
const pages=Math.floor(list.length/10)
let html=``;
for (let i=0; i<pages; i++) {
if (i===0) {
html+=`
<li>
<a class="active" href="#">${i+1}</a>
</li>`;
} else {
html+=`
<li>
${i+1}
</li>`;
}
}
const ul=document.createElement("ul");
ul.innerHTML=html;
const div=document.createElement("div");
div.classList.add("pagination");
div.appendChild(ul);
page.appendChild(div);
}
displayPage(listItems,1);
addPaginationLinks(listItems);
addEventListener=()=> {
const a=document.querySelectorAll("a");
a.forEach(item=> {
item.addEventListener('click', (e)=> {
a.forEach(item=> {
if (item.classList.contains("active")) {
item.classList.remove("active")
e.target.classList.add("active");
}
})
const pageNumber=parseInt(e.target.textContent);
displayPage(listItems,pageNumber);
})
})
}
addEventListener();
addSearchComponent=()=> {
const pageHeader=document.querySelector(".page-header.cf")
let html=`
<div class="student-search">
<input placeholder="Search for students...">
<button>Search</button>
</div>`;
pageHeader.insertAdjacentHTML('beforeend', html);
}
addSearchComponent()
const search=document.querySelector("input");
const studentDetails=document.getElementsByClassName("student-details");
noResultsItem=()=> {
const item=`
<li class="no-results" style="display: none;">
<h3>No Results Shown Found</h3>
</li>`;
studentList.insertAdjacentHTML('beforeend', item);
}
noResultsItem()
//select no results list item
//filtering student list based on keyup search value
//array length is 0 if no match was found
search.addEventListener('keyup', (e)=> {
const noResults=document.querySelector(".no-results");
const array=Array.from(studentDetails).filter(student=>student.children[1].textContent.includes(e.target.value))
//no items in filter
//make every student item dissppear, make noresults show
if (array.length==0) {
Array.from(studentDetails).forEach(student=>{
student.parentNode.style.display="none";
});
noResults.style.display="block";
//show ones that match, hide ones that don't
} else if (array.length>0) {
noResults.style.display="none";
Array.from(studentDetails).forEach(student=>{
if (student.children[1].textContent.includes(e.target.value)) {
student.parentNode.style.display="block";
} else {
student.parentNode.style.display="none";
}
});
}
});
Here is a link to my JSfiddle:
https://jsfiddle.net/dt7q9h5x/1/
The index.html shown above actually includes, 54 li list items, but I only added one for the sample.
Heres my issue:
Although the number of list items changes depending on the value in the input field, when I delete what I typed in and the search input becomes empty, all 54 items re-appear, which isn't supposed to happen because only 10 items are supposed to appear on each page.
I want it to go back to the original pagination setting, which was initially set before I typed anything into the search input. This was originally set by the function displayPages(listItems,1).
When you go to JSfiddle, you can see there are 10 items listed on each page. Once you start typing, things seem to work, but once you delete everthing, then all the items just re-appear.
I think the code at the bottom of app.js where I'm writing code for the event handler must be the culprit here.
Any suggestions?
Thanks in advance.
I checked your code, you didn't specify what to happen if the value of your search input becomes empty and this is where the problem occurs.
I changed your code a little and add a simple if statement and everything works just fine :)
search.addEventListener('keyup', (e) => {
// **************
// Check to see if search term is empty or not
// **************
if (e.target.value.length > 0) {
const noResults = document.querySelector(".no-results");
const array = Array.from(studentDetails).filter(student => student.children[1].textContent.includes(e.target.value))
//no items in filter
//make every student item dissppear, make noresults show
if (array.length == 0) {
Array.from(studentDetails).forEach(student => {
student.parentNode.style.display = "none";
});
noResults.style.display = "block";
//show ones that match, hide ones that don't
} else if (array.length > 0) {
noResults.style.display = "none";
Array.from(studentDetails).forEach(student => {
if (student.children[1].textContent.includes(e.target.value)) {
student.parentNode.style.display = "block";
} else {
student.parentNode.style.display = "none";
}
});
}
}
// **************
// Display the initial state if search term is empty
// **************
else {
displayPage(listItems, 1);
}
});