I built an app which add a class when i click on specified element. I want to add an additional functionality to my app. When i will click on an item i want to add my class clicked ( now the app works in the same way), but when i will click on another item i want to add the class on the last item and to remove the class from the first item.
document.addEventListener("DOMContentLoaded", () => {
const list = document.querySelectorAll('.item');
for (let i = 0; i < list.length; i++) {
list[i].addEventListener('click', function (e) {
const curentItem = e.target;
if (curentItem) {
curentItem.classList.add('clicked');
console.log(curentItem)
}
})
}
});
const arr = [1, 2, 3, 4]
const mapping = arr.map(item => `<li class="item">${item}</li>`);
document.querySelector('.items').innerHTML = mapping.join(' ');
.clicked {
background-color: blue;
}
<div class="app">
<ul class="items">
</ul>
</div>
How to change my code to get the specified result?
You can loop through list and remove class from each item and then add to the last item
document.addEventListener("DOMContentLoaded", () => {
const list = document.querySelectorAll('.item');
for (let i = 0; i < list.length; i++) {
list[i].addEventListener('click', function (e) {
const curentItem = e.target;
if (curentItem) {
list.forEach(item => item.classList.remove('clicked'))
curentItem.classList.add('clicked');
console.log(curentItem)
}
})
}
});
const arr = [1, 2, 3, 4]
const mapping = arr.map(item => `<li class="item">${item}</li>`);
document.querySelector('.items').innerHTML = mapping.join(' ');
.clicked {
background-color: blue;
}
<div class="app">
<ul class="items">
</ul>
</div>
Further more you shouldn't create html element using innerHTML and after that use
querySelectorAll() and select the elements.
A better and clean way for same functionality is like.
const ul = document.querySelector('.items');
const listItems = [];
document.addEventListener("DOMContentLoaded", () => {
initalizeItems([1, 2, 3, 4])
});
function initalizeItems(items){
items.forEach(text => {
const li = document.createElement('li');
li.innerHTML = text;
li.addEventListener('click', onItemClick)
listItems.push(li);
ul.appendChild(li)
})
}
function onItemClick(){
listItems.forEach(item => item.classList.remove('clicked'));
this.classList.add('clicked');
}
.clicked {
background-color: blue;
}
<div class="app">
<ul class="items">
</ul>
</div>
Just remove the class white looping all items inside the click event listner :
.....
for (let i = 0; i < list.length; i++) {
list[i].classList.remove('clicked');
.....
document.addEventListener("DOMContentLoaded", () => {
const list = document.querySelectorAll('.item');
for (let i = 0; i < list.length; i++) {
list[i].addEventListener('click', function (e) {
for (let i = 0; i < list.length; i++){
list[i].classList.remove('clicked');
// querySelectorAll return an array of dom elements, u can access them directly.
}
const curentItem = e.target;
if (curentItem){
curentItem.classList.add('clicked');
console.log(curentItem)
}
})
}
});
const arr = [1, 2, 3, 4]
const mapping = arr.map(item => `<li class="item">${item}</li>`);
document.querySelector('.items').innerHTML = mapping.join(' ');
.clicked {
background-color: blue;
}
<div class="app">
<ul class="items">
</ul>
</div>
Just use Element.classList.toggle, and as a boolean (toggle takes an optional second argument that basically says add if true, and remove if false), pass currentlyIteratedItem === clickedItem:
document.addEventListener("DOMContentLoaded", () => {
const list = document.querySelectorAll('.item');
for (const listItem of list) {
listItem.addEventListener('click', function(e) {
const clickedItem = e.target;
for (const currentlyIteratedItem of list) {
currentlyIteratedItem.classList.toggle('clicked', currentlyIteratedItem === clickedItem);
}
})
}
});
const arr = [1, 2, 3, 4]
const mapping = arr.map(item => `<li class="item">${item}</li>`);
document.querySelector('.items').innerHTML = mapping.join(' ');
.clicked {
background-color: blue;
}
<div class="app">
<ul class="items">
</ul>
</div>
Related
I'm trying to make a randomly placed puzzle with clicking, but it looks too rigid without any transition. I want to make the shuffle movement smoother with some delay or some transitions, so I tried to add CSS transition in each list item, but it doesn't really change anything. This is what I tried. How can I add CSS property to elements JS created?
<div class="wrap-all">
<ul class="image-container">
</ul>
</div>
<div class="container"></div>
<script>
const container = document.querySelector(".image-container");
const wrapAll = document.querySelector(".wrap-all");
const tileCount = 16;
let tiles = [];
setGame();
function setGame() {
tiles = createImageTiles();
tiles.forEach(tile => container.appendChild(tile));
}
wrapAll.addEventListener('click', () => {
const li = document.querySelector("li");
li.style.transitionDelay = "2s";
shuffle(tiles).forEach(tile => container.appendChild(tile));
setTimeout(() => {
container.innerHTML = "";
setGame();
}, 3000)
})
function createImageTiles() {
const tempArray = [];
Array(tileCount).fill().forEach((_, i) => {
const li = document.createElement("li");
li.setAttribute('data-index', i)
li.classList.add(`list${i}`);
tempArray.push(li)
})
return tempArray;
}
function shuffle(array) {
let index = array.length - 1;
while (index > 0) {
const randomIndex = Math.floor(Math.random() * (index + 1));
[array[index], array[randomIndex]] = [array[randomIndex], array[index]]
index--;
}
return array;
}
</script>
To add CSS transition to elements created by JavaScript, you need to add the transition property to the CSS style of the element. In your case, you can add the transition property to the "li" element style in the createImageTiles() function like this:
<div class="wrap-all">
<ul class="image-container">
</ul>
</div>
<div class="container"></div>
<script>
const container = document.querySelector(".image-container");
const wrapAll = document.querySelector(".wrap-all");
const tileCount = 16;
let tiles = [];
setGame();
function setGame() {
tiles = createImageTiles();
tiles.forEach(tile => container.appendChild(tile));
}
wrapAll.addEventListener('click', () => {
// select all "li" elements
const liList = document.querySelectorAll("li");
// add delay to transition for all "li" elements
liList.forEach(li => li.style.transitionDelay = "0.2s");
shuffle(tiles).forEach(tile => container.appendChild(tile));
setTimeout(() => {
container.innerHTML = "";
setGame();
}, 3000)
})
function createImageTiles() {
const tempArray = [];
Array(tileCount).fill().forEach((_, i) => {
const li = document.createElement("li");
li.setAttribute('data-index', i)
li.classList.add(`list${i}`);
// add transition property to "li" element
li.style.transition = "all 0.5s ease-in-out";
tempArray.push(li)
})
return tempArray;
}
function shuffle(array) {
let index = array.length - 1;
while (index > 0) {
const randomIndex = Math.floor(Math.random() * (index + 1));
[array[index], array[randomIndex]] = [array[randomIndex], array[index]]
index--;
}
return array;
}
</script>
const generateList = array => {
const newList = document.createElement("ul");
document.body.appendChild(newList);
for(let i = 0; i < array.length; i += 1) {
if(!Array.isArray(array[i])) {
const newItem = document.createElement("li");
newList.appendChild(newItem);
newItem.innerHTML = array[i];
} else if (Array.isArray(array[i])) {
const get = array[i];
const nestedList = document.createElement("li");
document.querySelector("ul").appendChild(nestedList);
const newUl = document.createElement("ul");
document.querySelector("li").appendChild(newUl);
const nestedItem = document.createElement("ul");
nestedList.appendChild(nestedItem);
for (let j = 0; j < get.length; j += 1) {
const nestedItemX = document.createElement("li");
nestedItem.appendChild(nestedItemX);
nestedItemX.innerHTML = get[j];
}
}
}
}
generateList([[0],1,2,[1.1,1.2,1.3],3,["One","Two","Three"]]);
Hello, I have task, make function which take array(for example[1,2,3[3.1,3.2],4]) and make list in HTML like this ul > li > 1 but if nested array its create new empty list. Must be like this, from start like this ul > li > ul > li
I wrote some code but its make inside li empty and I cannot understand why. Please correct me and explain why its happen?
Structure should be like on picture bellow
I see now, you were indeed creating some unneccesary ULs (only visible when inspecting sourcecode). I cleaned it up:
const generateList = array => {
const newList = document.createElement("ul");
for (let i = 0; i < array.length; i += 1){
const newItem = document.createElement("li");
if (!Array.isArray(array[i])){
newItem.innerHTML = array[i];
}
else{
const nestedArray = array[i];
const nestedList = document.createElement("ul");
for (let j = 0; j < nestedArray.length; j += 1){
const nestedItem = document.createElement("li");
nestedItem.innerHTML = nestedArray[j];
nestedList.appendChild(nestedItem);
}
newItem.appendChild(nestedList);
}
newList.appendChild(newItem);
}
document.body.appendChild(newList);
}
generateList([[0],1,2,[1.1,1.2,1.3],3,["One","Two","Three"]]);
function showMovies (dataMovie) {
const main = document.getElementById('main');
main.innerHTML = '';
for (let i = 0; i < dataMovie.length; i++) {
const newMovie = document.createElement('div');
newMovie.innerHTML =
`<div class="movie-img">
<img src="${url_poster + dataMovie[i].poster_path}" alt="${dataMovie[i].title}-poster">
</div>
<div class="movie-info">
<h3>${dataMovie[i].title}</h3>
<div class="genres">
</div>
<p>${dataMovie[i].release_date}</p>
</div>
<div class="movie-overview">
<h3>Synopsis:</h3><br>
<p>${dataMovie[i].overview}</p>
</div>`
main.appendChild(newMovie);
for (let j = 0; j < genresList.length; j++) {
dataMovie[i].genre_ids.forEach(id => {
if (genresList[j].id === id) {
let g = '';
const div = document.querySelector('.genres');
const p = document.createElement('p');
g += genresList[j].name;
p.innerHTML = `<p>- ${g} </p>`
div.appendChild(p);
}
});
}
}
I want to display all genres of one movie.
When i get one movie i've got no problem, when i get more than one, the first takes all the genres and no genres are displayed for the others.
movieData is my data.results of the api (tmdb).
You are selecting the same .genres all the time.
Try this please:
const div = newMovie.querySelector('.genres');
function showMovies(dataMovie) {
const main = document.getElementById('main');
main.innerHTML = '';
for (let i = 0; i < dataMovie.length; i++) {
const newMovie = document.createElement('div');
newMovie.innerHTML =
`<div class="movie-img">
<img src="{url_poster + dataMovie[i].poster_path}" alt="{dataMovie[i].title}-poster">
</div>
<div class="movie-info">
<h3>{dataMovie[i].title}</h3>
<div class="genres">
</div>
<p>{dataMovie[i].release_date}</p>
</div>
<div class="movie-overview">
<h3>Synopsis:</h3><br>
<p>{dataMovie[i].overview}</p>
</div>`
main.appendChild(newMovie);
for (let j = 0; j < genresList.length; j++) {
dataMovie[i].genre_ids.forEach(id => {
if (genresList[j] === id) {
let g = '';
const div = newMovie.querySelector('.genres');
const p = document.createElement('p');
g += genresList[j].name;
p.innerHTML = `<p>- ${g} </p>`
div.appendChild(p);
}
});
}
}
}
var genresList = [1,2,3,4,5,6,7,8]
showMovies([{genre_ids:[1,2,3]},{genre_ids:[4,5,6]}])
<div id="main"></div>
The problem is that when you write const div = document.querySelector('.genres'); you are always selecting the first element on the page that matches the selector.
One idea would be to add an id to the movie container, which you can then use on your query.
For instance something like this:
when you create the container div:
const newMovie = document.createElement('div');
newMovie.classList.add('new-movie');
newMovie.setAttribute('movie-id', dataMovie[i].id);
then on your selector:
const div = document.querySelector(`.new-movie[movie-id="${dataMovie[i].id}"] .genres`);
This would give you the .genres div inside the correct container (instead of the first one on the page).
For a different approach you could also try to use
const div = newMovie.querySelector('.genres');
instead of
const div = document.querySelector('.genres');
Which should give you the same result
I am creating a website that scrapes data from different websites and display them on my website by rendering the text file data.
I know how to do it in React (see my code below), but I want to do it in HTML using simple javascript. Please suggest me what I should do?
renderComments({comments}){
if(comments != null){
const commentList=comments.map((comment)=>{
return(
<li key={comment.id}>
<p> {comment.detail}</p>
<p>--{comment.author}</p>
</li>
);
});
return(
<div className="col-12 col-md-5 m-1">
<h4>Comments</h4>
<ul className='list-unstyled'>
{commentList}
</ul>
</div>
);
}
}
You can use document.createElement(tagName[, options]);
for (var i = 0; i < comments.length; i++) {
var comments = comments[i];
var ul = document.getElementsByClassName("list-unstyled")[0];
var li = document.createElement('li');
li.appendChild(document.createTextNode(comments));
ul.appendChild(li);
}
<ul className='list-unstyled'></ul>
Also you can use appendChild
for (var i = 0; i < comments.length; i++) {
var comments = comments[i];
var li = document.createElement('li');
li.innerHTML = comments;
document.getElementsByClassName("list-unstyled")[0].appendChild(li);
}
Here is the Vanilla JS equivalent, except the content of the child elements, which I left for you to finish using the same methods I've used:
function renderComments(comments) {
if (comments != null) {
let ans = document.createElement('div');
ans.classList.add('col-12', 'col-md-5', 'm-1');
let h4Node = document.createElement('h4');
h4Node.innerText = 'Comments';
ans.appendChild(h4Node);
let ulNode = document.createElement('ul');
ulNode.classList.add('list-unstyled');
for (let i = 0; i < comments.length; i++) {
let comment = comments[i];
let currNode = document.createElement('li');
currNode.setAttribute('key', comment.id);
// Here add the additional two <p> elements
ulNode.appendChild(currNode);
}
ans.appendChild(ulNode);
return ans;
}
}
let example = [{id: 1}, {id: 2}];
document.body.appendChild(renderComments(example));
The core of this are createElement and appendChild, which creates an HTML node and adds an element as a child, respectively.
I got the tabs to work but the issue is that I really don't think this follows javascript's don't repeat yourself. As you can see from my code I had to create individual loops for each tab so just that tab would show. The idea is you click the tab and just the description block for that particular tab would show. click tab[0] show just descriptionBlock[0] etc...i also want the initial descriptionBlock to show on pageload. Please no jquery, ES6 javascript is the ideal. Any suggestions would be helpful...
//Product Overview Tabs
const tabs = document.querySelectorAll('.tablist li');
const descriptionBlock = document.querySelectorAll('.descriptionBlock');
for (let i = 0; i < tabs.length; i++) {
tabs[i].addEventListener('click', (ev) => { ev.preventDefault(); });
}
tabs[0].onclick = () => {
descriptionBlock[0].style.display = "block";
for (let i = 1; i < descriptionBlock.length; i++) {
descriptionBlock[i].style.display = "none";
}
}
tabs[1].onclick = () => {
descriptionBlock[1].style.display = "block";
for (let i = 0; i < descriptionBlock.length; i++) {
if (i === 1) {
continue;
}
descriptionBlock[i].style.display = "none";
}
}
tabs[2].onclick = () => {
descriptionBlock[2].style.display = "block";
for (let i = 0; i < descriptionBlock.length; i++) {
if (i === 2) {
continue;
}
descriptionBlock[i].style.display = "none";
}
}
tabs[3].onclick = () => {
descriptionBlock[3].style.display = "block";
for (let i = 0; i < descriptionBlock.length; i++) {
if (i === 3) {
continue;
}
descriptionBlock[i].style.display = "none";
}
}
tabs[4].onclick = () => {
descriptionBlock[4].style.display = "block";
for (let i = 0; i < descriptionBlock.length; i++) {
if (i === 4) {
continue;
}
descriptionBlock[i].style.display = "none";
}
}
tabs[5].onclick = () => {
descriptionBlock[5].style.display = "block";
for (let i = 0; i < descriptionBlock.length; i++) {
if (i === 5) {
continue;
}
descriptionBlock[i].style.display = "none";
}
}
tabs[6].onclick = () => {
descriptionBlock[6].style.display = "block";
for (let i = 0; i < descriptionBlock.length; i++) {
if (i === 6) {
continue;
}
descriptionBlock[i].style.display = "none";
}
}
tabs[7].onclick = () => {
descriptionBlock[7].style.display = "block";
for (let i = 0; i < 8; i++) {
if (i === 7) {
continue;
}
descriptionBlock[i].style.display = "none";
}
}
tabs[8].onclick = () => {
descriptionBlock[8].style.display = "block";
for (let i = 0; i < 8; i++) {
descriptionBlock[i].style.display = "none";
}
}
<div class="overview-wrapper" id="overview">
<h2>Pro<span>duct Overv</span>iew</h2>
<div>
<div class="product-tabs">
<div class="miniproduct-wrapper">
<img src="/_img/models/Model_J.png" alt="Model J" title="Model J" width="150" height="150">
<h3>Model J6</h3>
</div>
<div>
<div class="down-arrow"></div>
</div>
<ul class="tablist">
<li>Key Features</li>
<li>Specifications</li>
<li>Dimensions</li>
<li>Product Builder</li>
<li>Sizing Chart</li>
<li>Dish Machine<br>Cross Reference<br>Chart</li>
<li>Options</li>
<li>Support</li>
<li>Faqs</li>
</ul>
</div>
<div class="description-tabs">
<div class="descriptionBlock" style="display:block;">1</div>
<div class="descriptionBlock">2</div>
<div class="descriptionBlock">3</div>
<div class="descriptionBlock">4</div>
<div class="descriptionBlock">5</div>
<div class="descriptionBlock">6</div>
<div class="descriptionBlock">7</div>
<div class="descriptionBlock">8</div>
<div class="descriptionBlock">9</div>
</div>
</div>
</div>
Define a function showBlock(index) that loops over blocks and shows the one with corresponding index. Loop over tabs and add event listener that calls showBlock with corresponding index. Also you might want to google for event delegation to avoid adding multiple event listeners.
const tabs = document.querySelectorAll('.tablist li')
const descriptionBlocks = document.querySelectorAll('.descriptionBlock')
const entries = function*(iterable) {
let i = 0;
for (item of iterable) {
yield [i++, item]
}
}
const showBlock = index => {
for (const [blockIndex, block] of entries(descriptionBlocks)) {
block.style.display = blockIndex === index ? 'block' : 'none'
}
}
showBlock(0)
for (const [tabIndex, tab] of entries(tabs)) {
tab.addEventListener('click', ev => {
ev.preventDefault()
showBlock(tabIndex)
})
}
<div class="overview-wrapper" id="overview">
<h2>Pro<span>duct Overv</span>iew</h2>
<div>
<div class="product-tabs">
<div class="miniproduct-wrapper">
<img src="/_img/models/Model_J.png" alt="Model J" title="Model J" width="150" height="150">
<h3>Model J6</h3>
</div>
<div>
<div class="down-arrow"></div>
</div>
<ul class="tablist">
<li>Key Features</li>
<li>Specifications</li>
<li>Dimensions</li>
<li>Product Builder</li>
<li>Sizing Chart</li>
<li>Dish Machine<br>Cross Reference<br>Chart</li>
<li>Options</li>
<li>Support</li>
<li>Faqs</li>
</ul>
</div>
<div class="description-tabs">
<div class="descriptionBlock" style="display:block;">1</div>
<div class="descriptionBlock">2</div>
<div class="descriptionBlock">3</div>
<div class="descriptionBlock">4</div>
<div class="descriptionBlock">5</div>
<div class="descriptionBlock">6</div>
<div class="descriptionBlock">7</div>
<div class="descriptionBlock">8</div>
<div class="descriptionBlock">9</div>
</div>
</div>
</div>
If I clearly understand, just save current selected index and update in click handler:
const tabs = document.querySelectorAll('.tablist li');
const descriptions = document.querySelectorAll('.descriptionBlock');
let selectedIndex = 0;
tabs.forEach((tab, i) => {
// if you really need to do initial "none" assignment in js
// instead add display none to your css
descriptions[i].style.display = "none";
tab.addEventListener('click', (ev) => {
ev.preventDefault();
descriptions[selectedIndex].style.display = "none";
descriptions[i].style.display = "block";
selectedIndex = i;
});
});
// select default tab
tabs[selectedIndex].click();
Profit!
P.S. You can save whole description block, but if you store index it will be helpful for working with both selected tab and description block.
You can just put the common code in a function which I will call showTab() and then use a loop to add all the click listeners and then just call the common function from each listener:
const tabs = Array.from(document.querySelectorAll('.tablist li'));
const descriptionBlock = document.querySelectorAll('.descriptionBlock');
function showTab(n) {
for (let i = 0; i < descriptionBlock.length; i++) {
descriptionBlock[i].style.display = "none";
}
descriptionBlock[n].style.display = "block";
}
for (let [i, tab] of tabs.entries()) {
tab.addEventListener('click', (ev) => {
ev.preventDefault();
showTab(i);
});
}