How to change the value of a variable depending on a function? - javascript

I'm trying to build a small program that shows how long it takes a certain vehicle to orbit a planet. Eg a car at 100mph orbits earth (7917 miles) in 79.17 hours and etc.
To display vehicles I've used icons and to display planets I've used img. I can give values to icons (in this case I've given them their speed values as in mph) But for images I can't give values for some reason which I wanted to use as the radius of the planets.
Currently I have
let icons = [...document.querySelectorAll('.icon-container .fa')];
let answerContainer = document.getElementById("answer-container");
let answerDiv = document.getElementById("answer-div");
let planetImgs = [...document.querySelectorAll(".planet-container img")];
//Function to slide icons User clicks right/left to show next/previous vehicle icon
function adjustActive (adjustment) {
var current = icons.find(it => it.id === 'active');
var currentIndex = icons.indexOf(current);
var nextIndex = (currentIndex + adjustment) % icons.length;
if (nextIndex < 0) nextIndex = icons.length - 1;
current.removeAttribute('id');
icons[nextIndex].id = 'active';
// Action to find Orbital Period
let planet = 7917.5; **(I WANT THIS TO CHANGE DEPENDING ON THE FUNCTION BELOW)**
let nextElement = document.getElementById("active");
let nextElements = nextElement.getAttribute("value")
let orbitalPeriod = planet / nextElements;
answerDiv.innerHTML = orbitalPeriod.toFixed(2);
}
// Function to slide planets - User clicks right/left to show next/previous planet
function adjustActivePlanet (adjustment) {
var current = planetImgs.find(it => it.id === 'active-planet');
var currentIndex = planetImgs.indexOf(current);
var nextIndex = (currentIndex + adjustment) % icons.length;
if (nextIndex < 0) nextIndex = planetImgs.length - 1;
current.removeAttribute('id');
planetImgs[nextIndex].id = 'active-planet';
} // The value of this function needs to be used in the function above for let "planets"
//Arrow Buttons
document.querySelector('#left-arrow').addEventListener('click', e => adjustActive(-1));
document.querySelector('#right-arrow').addEventListener('click', e => adjustActive(1));
document.querySelector('#left-arrow-planet').addEventListener('click', e => adjustActivePlanet(-1));
document.querySelector('#right-arrow-planet').addEventListener('click', e => adjustActivePlanet(1));
<div class="main-container">
<div class="object-container">
<div class="icon-container">
<i class="fa fa-car" id="active" value="100"></i>
<i class="fa fa-bicycle" value="25"></i>
<i class="fa fa-plane" value="500"></i>
<i class="fa fa-ship" value="10"></i>
<i class="fa fa-fighter-jet" value="1000"></i>
<i class="fa fa-space-shuttle" value="3000"></i>
</div>
<div class="arrow-buttons">
</div>
</div>
<div class="answer-container" id="answer-container">
<div id="answer-div"></div>
<h3>Hours to orbit</h3>
</div>
<div class="planet-container">
<img src="images/earth planet.png" alt="" id="active-planet">
<img src="images/saturn planet.png" alt="" class="planet">
<img src="images/neptune planet.png" alt="" class="planet">
<img src="images/mercury planet.png" alt="" class="planet">
<img src="images/sun.png" alt="" class="planet">
<img src="images/mars planet.png" alt="" class="planet">
<div class="arrow-buttons">
</div>
</div>
</div>
</div>
As you can see above, the let planet = 7917.5 is earths radius, so the answer container will output the value of "planet" / the value of the active icons "active". How can I make the value of "planet" change depending on which planet is currently active ("active-planet"). For example neptune is 30,000 so I want the "planet" value to be 30,000 / the value of the icon (car 100mph).
Any help will be greatly appreciated.

Instead of using the value attribute you can use a data-<name> attribute.
ie.
<img src="images/neptune planet.png" alt="" class="planet" data-radius="30000">
Then you can access this value with:
document.querySelector('#active-planet').dataset.radius
EDIT: I've added a runnable snippet example to show how I would approach your program.
I find it helps to break down the related logic into individual functions, particularly when I want to use the same logic in multiple places (in each button listener).
In this case ive created a setActiveClasses() function and renderOrbitDuration() function.
We keep track of the active vehicle and planet using an index (0, 1, 2, etc...) for each (activeVehicleIndex and activePlanetIndex).
The buttons simply increment and decrement the relevant index and then the setActiveClasses() function and renderOrbitDuration() function use this index to set the right active classes and render the orbital duration.
Read through and run the snippet below.
let activeVehicleIndex = 0
let activePlanetIndex = 0
const vehicleElements = document.querySelectorAll('.vehicles i')
const planetElements = document.querySelectorAll('.planets img')
function setActiveClasses () {
// Clear old active classes
[...vehicleElements, ...planetElements].forEach(el => {
el.classList.remove('active')
})
// Add new active classes
vehicleElements[activeVehicleIndex].classList.add('active')
planetElements[activePlanetIndex].classList.add('active')
}
function renderOrbitDuration () {
activeVehicleEl = vehicleElements[activeVehicleIndex]
activePlanetEl = planetElements[activePlanetIndex]
const html = `
Vehicle Name: ${activeVehicleEl.dataset.name}<br>
Vehicle Speed: ${activeVehicleEl.dataset.speed}<br>
Planet Name: ${activePlanetEl.dataset.name}<br>
Planet Radius: ${activePlanetEl.dataset.radius}<br>
Orbital Period: ${activePlanetEl.dataset.radius / activeVehicleEl.dataset.speed}
`
document.querySelector('.output').innerHTML = html
}
// Previous/Next Button Listeners
document.querySelector('.vehicles .prev').addEventListener('click', () => {
activeVehicleIndex-- // Decrement activeVehicleIndex
if (activeVehicleIndex < 0) { activeVehicleIndex = 0 } // Dont allow negative values
setActiveClasses()
renderOrbitDuration()
})
document.querySelector('.vehicles .next').addEventListener('click', () => {
activeVehicleIndex++ // Increment activeVehicleIndex
if (activeVehicleIndex > vehicleElements.length - 1) { activeVehicleIndex = vehicleElements.length - 1 } // Dont allow values greater than number of vehicles
setActiveClasses()
renderOrbitDuration()
})
document.querySelector('.planets .prev').addEventListener('click', () => {
activePlanetIndex-- // Decrement activePlanetIndex
if (activePlanetIndex < 0) { activePlanetIndex = 0 } // Dont allow negative values
setActiveClasses()
renderOrbitDuration()
})
document.querySelector('.planets .next').addEventListener('click', () => {
activePlanetIndex++ // Increment activePlanetIndex
if (activePlanetIndex > planetElements.length - 1) { activePlanetIndex = planetElements.length - 1 } // Dont allow values greater than number of planets
setActiveClasses()
renderOrbitDuration()
})
// Run both of these once on page load to set initial classes and render values
setActiveClasses()
renderOrbitDuration()
.vehicles, .planets {
margin-bottom: 1em;
}
.vehicles .active {
color: red;
}
.planets .active {
border: 1px solid red;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" rel="stylesheet"/>
<!-- Vehicles -->
<div class="vehicles">
<i class="fa fa-car" data-name="car" data-speed="100"></i>
<i class="fa fa-bicycle" data-name="bike" data-speed="25"></i>
<i class="fa fa-plane" data-name="plane" data-speed="500"></i>
<i class="fa fa-ship" data-name="ship" data-speed="10"></i>
<i class="fa fa-fighter-jet" data-name="fighter jet" data-speed="1000"></i>
<i class="fa fa-space-shuttle" data-name="space shuttle" data-speed="3000"></i>
<div>
<button class="prev">Prev</button>
<button class="next">Next</button>
</div>
</div>
<!-- Planets -->
<div class="planets">
<img src="http://via.placeholder.com/50" data-name="earth" data-radius="6000"/>
<img src="http://via.placeholder.com/50" data-name="saturn" data-radius="60000"/>
<img src="http://via.placeholder.com/50" data-name="neptune" data-radius="25000"/>
<img src="http://via.placeholder.com/50" data-name="mercury" data-radius="2500"/>
<img src="http://via.placeholder.com/50" data-name="sun" data-radius="700000"/>
<img src="http://via.placeholder.com/50" data-name="mars" data-radius="3500"/>
<div>
<button class="prev">Prev</button>
<button class="next">Next</button>
</div>
</div>
<!-- Output -->
<div class="output"></div>

Related

HTML + JavaScript custom player for multiple videos on a website page

EDIT: I have found the solution, posted and marked as the best answer below.
I'm coding a portfolio website for myself using HTML, CSS and JS and I need to add multiple videos on a lot of pages.
I followed some tutorials to learn how to customize the video player, but it only works for one specific video on the page. If I were to add more videos, I'd need to have one .js custom player file for each video and manually select them on the website.
How can I apply this single .js custom video player to all of my videos using purely javascript?
I have found similar topics about the subject here, but all of them uses jQuery and I'm struggling to make it work with javascript.
My HTML for the video player:
<section class="videoplayer">
<div class="c-video">
<div id="video_player">
<video src="./media/portfolio/videos/Show-Reel-2021.mp4" id="main-video"></video>
<div class="progressAreaTime">00:00</div>
<div class="controls">
<div class="progress-area">
<div class="progress-bar">
<span></span>
</div>
<div class="buffered-progress-bar"></div>
</div>
<div class="controls-list">
<div class="controls-left">
<span class="icon">
<i class="material-icons fast-rewind" title="Retroceder 10 segundos">first_page</i>
</span>
<span class="icon">
<i class="material-icons play_pause" title="Reproduzir">play_arrow</i>
</span>
<span class="icon">
<i class="material-icons fast-forward" title="Avançar 10 segundos">last_page</i>
</span>
<span class="icon">
<i class="material-icons volume" title="Sem áudio">volume_up</i>
<input type="range" min="0" max="100" class="volume_range">
</span>
<div class="timer">
<span class="current">00:00</span> / <span class="duration">0:00</span>
</div>
</div>
<div class="controls-right">
<span class="icon">
<i class="material-icons auto-play" title="A repetição automática está desativada"></i>
</span>
<span class="icon">
<i class="material-icons settingsBtn" title="Detalhes">settings</i>
</span>
<span class="icon">
<i class="material-icons picture_in_picture" title="Miniplayer">picture_in_picture_alt</i>
</span>
<span class="icon">
<i class="material-icons fullscreen" title="Tela inteira">fullscreen</i>
</span>
</div>
</div>
</div>
<div id="settings">
<div class="playback">
<span>Velocidade da Reprodução</span>
<ul>
<li data-speed="0.25">0.25</li>
<li data-speed="0.5">0.5</li>
<li data-speed="0.75">0.75</li>
<li data-speed="1" class="active">Normal</li>
<li data-speed="1.25">1.25</li>
<li data-speed="1.5">1.5</li>
<li data-speed="1.75">1.75</li>
<li data-speed="2">2</li>
</ul>
</div>
</div>
</div>
</div>
My JavaScript for the video player:
// Select elements
const vidsrc = document.querySelector('#main-video').src;
const video_player = document.querySelector('#video_player'),
mainVideo = video_player.querySelector('#main-video'),
progressAreaTime = video_player.querySelector('.progressAreaTime'),
controls = video_player.querySelector('.controls'),
progressArea = video_player.querySelector('.progress-area'),
progress_Bar = video_player.querySelector('.progress-bar'),
buffered_Bar = video_player.querySelector('.buffered-progress-bar'),
fast_rewind = video_player.querySelector('.fast-rewind'),
play_pause = video_player.querySelector('.play_pause'),
fast_forward = video_player.querySelector('.fast-forward'),
volume = video_player.querySelector('.volume'),
volume_range = video_player.querySelector('.volume_range'),
current = video_player.querySelector('.current'),
totalDuration = video_player.querySelector('.duration'),
auto_play = video_player.querySelector('.auto-play'),
settingsBtn = video_player.querySelector('.settingsBtn'),
picture_in_picture = video_player.querySelector('.picture_in_picture'),
fullscreen = video_player.querySelector('.fullscreen'),
settings = video_player.querySelector('#settings'),
playback = video_player.querySelectorAll('.playback li');
mainVideo.addEventListener('loadeddata',()=>{
setInterval(() => {
let bufferedTime = mainVideo.buffered.end(0);
let duration = mainVideo.duration;
let width = (bufferedTime / duration) * 100;
buffered_Bar.style.width = `${width}%`
}, 500);
})
// Play
function playVideo() {
play_pause.innerHTML = "pause";
play_pause.title = "Pausar";
video_player.classList.add('paused')
mainVideo.play();
}
// Pause
function pauseVideo() {
play_pause.innerHTML = "play_arrow";
play_pause.title = "Reproduzir";
video_player.classList.remove('paused')
mainVideo.pause();
}
play_pause.addEventListener('click',()=>{
const isVideoPaused = video_player.classList.contains('paused');
isVideoPaused ? pauseVideo() : playVideo();
})
mainVideo.addEventListener('play',()=>{
playVideo();
})
mainVideo.addEventListener('pause',()=>{
pauseVideo();
})
// Rewind
fast_rewind.addEventListener('click',()=>{
mainVideo.currentTime -= 10;
})
// Forward
fast_forward.addEventListener('click',()=>{
mainVideo.currentTime += 10;
})
// Total duration
mainVideo.addEventListener("loadeddata",(e)=>{
let videoDuration = e.target.duration;
let totalMin = Math.floor(videoDuration / 60);
let totalSec = Math.floor(videoDuration % 60);
// Complete with zero at beggining
totalSec < 10 ? totalSec = "0"+totalSec:totalSec;
totalMin < 10 ? totalMin = "0"+totalMin:totalMin;
totalDuration.innerHTML = `${totalMin}:${totalSec}`;
})
// Current duration
mainVideo.addEventListener('timeupdate',(e)=>{
let currentVideoTime = e.target.currentTime;
let currentMin = Math.floor(currentVideoTime / 60);
let currentSec = Math.floor(currentVideoTime % 60);
// Complete with zero at beggining
currentSec < 10 ? currentSec = "0"+currentSec:currentSec;
currentMin < 10 ? currentMin = "0"+currentMin:currentMin;
current.innerHTML = `${currentMin}:${currentSec}`;
let videoDuration = e.target.duration
// Seek bar
let progressWidth = (currentVideoTime / videoDuration) * 100;
progress_Bar.style.width = `${progressWidth}%`;
})
// Update current duration
progressArea.addEventListener('click',(e)=>{
let videoDuration = mainVideo.duration;
let progressWidthval = progressArea.clientWidth;
let ClickOffsetX = e.offsetX;
mainVideo.currentTime = (ClickOffsetX / progressWidthval) * videoDuration;
})
// Volume
function changeVolume() {
mainVideo.volume = volume_range.value / 100;
if (volume_range.value == 0) {
volume.innerHTML = "volume_off";
}else if(volume_range.value < 40){
volume.innerHTML = "volume_down";
}else{
volume.innerHTML = "volume_up";
}
}
function muteVolume() {
if (volume_range.value == 0) {
volume_range.value = 80;
mainVideo.volume = 0.8;
volume.innerHTML = "volume_up";
volume.title = "Sem áudio";
}else{
volume_range.value = 0;
mainVideo.volume = 0;
volume.innerHTML = "volume_off";
volume.title = "Reativar o som";
}
}
volume_range.addEventListener('change',()=>{
changeVolume();
})
volume.addEventListener('click',()=>{
muteVolume();
})
// Update on mouse move
progressArea.addEventListener('mousemove',(e)=>{
let progressWidthval = progressArea.clientWidth;
let x = e.offsetX;
progressAreaTime.style.setProperty('--x',`${x}px`);
progressAreaTime.style.display = "block";
let videoDuration = mainVideo.duration;
let progressTime = Math.floor((x/progressWidthval)*videoDuration);
let currentMin = Math.floor(progressTime / 60);
let currentSec = Math.floor(progressTime % 60);
// Complete with zero at beggining
currentSec < 10 ? currentSec = "0"+currentSec:currentSec;
currentMin < 10 ? currentMin = "0"+currentMin:currentMin;
progressAreaTime.innerHTML = `${currentMin}:${currentSec}`;
})
progressArea.addEventListener('mouseleave',()=>{
progressAreaTime.style.display = "none";
})
// Loop
auto_play.addEventListener('click',()=>{
auto_play.classList.toggle('active')
if(auto_play.classList.contains('active')){
auto_play.title = "A repetição automática está ativada";
}else{
auto_play.title = "A repetição automática está desativada";
}
});
mainVideo.addEventListener("ended",()=>{
if (auto_play.classList.contains('active')) {
playVideo();
}else{
play_pause.innerHTML = "replay";
play_pause.title = "Reproduzir novamente";
}
});
// Picture in picture
picture_in_picture.addEventListener('click',()=>{
mainVideo.requestPictureInPicture();
})
// Full screen
fullscreen.addEventListener('click',()=>{
if (!video_player.classList.contains('openFullScreen')) {
video_player.classList.add('openFullScreen');
fullscreen.innerHTML = "fullscreen_exit";
fullscreen.title = "Sair da tela inteira";
video_player.requestFullscreen();
}else{
video_player.classList.remove('openFullScreen');
fullscreen.innerHTML = "fullscreen";
fullscreen.title = "Tela inteira";
document.exitFullscreen();
}
});
// Settings
settingsBtn.addEventListener('click',()=>{
settings.classList.toggle('active');
settingsBtn.classList.toggle('active');
})
// Speed
playback.forEach((event)=>{
event.addEventListener('click',()=>{
removeActiveClasses();
event.classList.add('active');
let speed = event.getAttribute('data-speed');
mainVideo.playbackRate = speed;
})
})
function removeActiveClasses() {
playback.forEach(event => {
event.classList.remove('active')
});
}
// Get URL
let xhr = new XMLHttpRequest();
xhr.open("GET",vidsrc);
xhr.responseType = "arraybuffer";
xhr.onload = (e)=>{
let blob = new Blob([xhr.response]);
let url = URL.createObjectURL(blob);
mainVideo.src = url;
}
xhr.send();
// Store duration
window.addEventListener('unload',()=>{
let setDuration = localStorage.setItem('duration',`${mainVideo.currentTime}`);
let setSrc = localStorage.setItem('src',`${mainVideo.getAttribute('src')}`);
})
window.addEventListener('load',()=>{
let getDuration = localStorage.getItem('duration');
let getSrc = localStorage.getItem('src');
if (getSrc) {
mainVideo.src = getSrc;
mainVideo.currentTime = getDuration;
}
})
mainVideo.addEventListener('contextmenu',(e)=>{
e.preventDefault();
})
// Hide and show controls (mouse)
video_player.addEventListener('mouseover',()=>{
controls.classList.add('active');
})
video_player.addEventListener('mouseleave',()=>{
if (video_player.classList.contains('paused')) {
if (settingsBtn.classList.contains('active')) {
controls.classList.add('active');
}else{
controls.classList.remove('active')
}
}else{
controls.classList.add('active')
}
})
if (video_player.classList.contains('paused')) {
if (settingsBtn.classList.contains('active')) {
controls.classList.add('active');
}else{
controls.classList.remove('active')
}
}else{
controls.classList.add('active')
}
// Hide and show controls (mobile)
video_player.addEventListener('touchstart',()=>{
controls.classList.add('active');
setTimeout(() => {
controls.classList.remove('active')
}, 8000);
})
video_player.addEventListener('touchmove',()=>{
if (video_player.classList.contains('paused')) {
controls.classList.remove('active')
}else{
controls.classList.add('active')
}
})
Helper function which you can use to write the HTML skeleton once and then have it dynamically created for multiple instances
function renderVideoPlayers() {
const sources = [
'../my-video-src-1',
'../my-video-src-2',
'../my-video-src-3',
'../my-video-src-4',
]
const videoPlayers = sources.map((video) => `
<section class="videoplayer">
<div class="c-video">
<div id="video_player">
<video src="${video}" class="video"></video>
<div class="progressAreaTime">00:00</div>
<div class="controls">
<div class="progress-area">
<div class="progress-bar">
<span></span>
</div>
<div class="buffered-progress-bar"></div>
</div>
<div class="controls-list">
<div class="controls-left">
<span class="icon">
<i class="material-icons fast-rewind" title="Retroceder 10 segundos">first_page</i>
</span>
<span class="icon">
<i class="material-icons play_pause" title="Reproduzir">play_arrow</i>
</span>
<span class="icon">
<i class="material-icons fast-forward" title="Avançar 10 segundos">last_page</i>
</span>
<span class="icon">
<i class="material-icons volume" title="Sem áudio">volume_up</i>
<input type="range" min="0" max="100" class="volume_range">
</span>
<div class="timer">
<span class="current">00:00</span> / <span class="duration">0:00</span>
</div>
</div>
<div class="controls-right">
<span class="icon">
<i class="material-icons auto-play" title="A repetição automática está desativada"></i>
</span>
<span class="icon">
<i class="material-icons settingsBtn" title="Detalhes">settings</i>
</span>
<span class="icon">
<i class="material-icons picture_in_picture" title="Miniplayer">picture_in_picture_alt</i>
</span>
<span class="icon">
<i class="material-icons fullscreen" title="Tela inteira">fullscreen</i>
</span>
</div>
</div>
</div>
<div id="settings">
<div class="playback">
<span>Velocidade da Reprodução</span>
<ul>
<li data-speed="0.25">0.25</li>
<li data-speed="0.5">0.5</li>
<li data-speed="0.75">0.75</li>
<li data-speed="1" class="active">Normal</li>
<li data-speed="1.25">1.25</li>
<li data-speed="1.5">1.5</li>
<li data-speed="1.75">1.75</li>
<li data-speed="2">2</li>
</ul>
</div>
</div>
</div>
</div>
`)
// get an element already on the page add the rendered
// html for each video into that section.
const container = document.getElementById('container')
container.innerHTML = videoPlayers.join("")
}
// call this function when you want it to be displayed
renderVideoPlayers()
<html>
<head>
</head>
<body>
<div id="container">
</div>
</body>
</html>
You will also need to do the same with Javascript as well as ensure that there aren't multiple instance of the same id. This shouldn't be too hard as you can switch similiar styles to class and make the id's something like video-1, video-2, video-3, etc.
A couple notes
sources: this is an array of all video urls you wish to display on the page.
sources.map: will iterate through the array and interpolate a string with the video player HTML and video source
videoPlayers: is now an array of HTML markup
container: will be a parent element on the page where you wish to append all the HTML markup
I HAVE FOUND THE SOLUTION
For anyone in the future seeking for the same solution, I was able to make it work by first selecting every videoplayer div:
const videoContainers = document.querySelectorAll(".video-container")
Then using the .forEach() function so that the code will generate a button for every individual videoplayer:
videoContainers.forEach((container) => {
let playPauseBtn = container.querySelector(".play-pause-btn");
let theaterBtn = container.querySelector(".theater-btn");
let fullScreenBtn = container.querySelector(".full-screen-btn");
...
// The entire code for one videoplayer
}
You can reference "container" for the individual videoplayer or "document" for every videoplayer at once. It just works like magic!

Creating a flip card with multiple faces using Javascript

I was trying to create a flipcard with multiple faces but Im not entirely sure how to make the change whenever I click the card. When I'm on the console I can see that the class was added but the face is still the same. I want it to be grey background, Name: Jane, grey background, Name: John. Thats the pattern I am trying to create. I have janeCard and johnCard at the end since I'm not 100% sure how to toggle between them.
Here is the link to my codepen if anyone wants to see. https://codepen.io/heidibonilla/pen/abyRrBg
<section class="card-area">
<div class="card">
<div class="inner-card jane" id="clicks">
<div class="front"></div>
</div>
</div>
// Card will flip on click
const flipCard = document.querySelector('.inner-card');
flipCard.addEventListener('click', function() {
flipCard.classList.toggle('is-flipped');
})
// Set a var to count the clicks
let clicks = document.getElementById('clicks');
let count = 0;
clicks.onclick = function() {
count += 1;
console.log(count);
if(count % 2 == 0) {
// show front of card
console.log('even');
} else {
// switch between john or jane
janeCard.classList.toggle('john');
console.log('odd');
console.log(janeCard)
}
}
const janeCard = document.querySelector('.jane');
const johnCard = document.querySelector('.john');
janeCard.innerHTML = `<div class="front">
</div>
<div class="back">
<div class="back-info">
<h2>Jane Doe</h2>
<p><strong>Title:</strong> Web Developer</p>
<p><strong>Email:</strong> example#example.com</p>
<p><strong>Phone:</strong> (999) 999 - 9999</p>
</div>
</div>`
johnCard.innerHTML = `<div class="back">
<div class="back-info">
<h2>John Doe</h2>
<p><strong>Title:</strong> iOS Developer</p>
<p><strong>Email:</strong> example#example.com</p>
<p><strong>Phone:</strong> (999) 999 - 9999</p>
</div>
</div>`

How to click on a child element

There many items and I get object of the desired item. But I don't know how can I click on the child element in this object.
html:
<div class="item">
<div role="button" tabindex="-1">
<strong>ItemName2</strong>
</div>
<div class="d">
<div class="item-icon" role="button" tabindex="-1" style="display: none">
<i aria-label="icon: add" class="add"></i> <!-- I need to click on this Item -->
</div>
<div class="item-icon" role="button" tabindex="-1" style="display: none">
<i aria-label="icon: del" class="del"></i>
</div>
</div>
</div>
<div class="item"> ... </div>
<div class="item"> ... </div>
<div class="item"> ... </div>
js:
let fBtns = await driver.findElements(By.tagName('strong')); // Find all objects
let buttons = fBtns.map(elem => elem.getText());
const allButtons = await Promise.all(buttons);
console.log(allButtons); // All object names
let current = fBtns[fBtns.length - 1];
console.log(current); // This is desired object
await current.click(); // This is click on the object and operates as expected
// But I need to click on the <i aria-label="icon: add" class="add"> element
// How can I click on the desired element?
To click the element <i aria-label="icon: del" class="del"></i>, you can just use an XPath to query directly on the element:
await driver.findElement(By.xpath("//div[div/strong[text()='ItemName2']]/div/div/i[#class='del']")).click()
You can probably shorten this a bit to:
await driver.findElement(By.xpath("//div[div/strong[text()='ItemName2']]//i[#class='del']")).click()
Try invoking click by trigger method:
$('.item-icon .add').trigger("click");
In the below example, I scan the document for a dynamic xpath that finds the strong with string ItemName2 and then traverse back up one level (/../) before moving back down to the child element. This will act like a waitForElement that you can hopefully repurpose to trigger a click.
var MyDefaultTimeout = 1500;
var loaded = false;
do {
var icon = document.getElementsByClassName('//*[contains(#strong,\'ItemName2\')]/../div/div/i');
if(!icon.length == 0)
{
setTimeout(function() { loaded = false }, MyDefaultTimeout);
}
else
{
if(!document.readyState === 'complete')
{
setTimeout(function() { loaded = false }, MyDefaultTimeout);
}
else
{
loaded = true;
return document.readyState;
}
}
}
while(loaded === false);

How can I make my Slideshow Indicators change their style with my current code?

I have had an issue with placing slideshows in my website. I require more than one slideshow on the same page which has led to multiple problems with the scrolling and such. I finally came across a solution which works almost perfectly however, the indicators do not change color to reflect the current slide.
var w3 = {};
w3.slideshow = function (sel, ms, func) {
var i, ss, x = w3.getElements(sel), l = x.length;
ss = {};
ss.current = 1;
ss.x = x;
ss.ondisplaychange = func;
if (!isNaN(ms) || ms == 0) {
ss.milliseconds = ms;
} else {
ss.milliseconds = 1000;
}
ss.start = function() {
ss.display(ss.current)
if (ss.ondisplaychange) {ss.ondisplaychange();}
if (ss.milliseconds > 0) {
window.clearTimeout(ss.timeout);
ss.timeout = window.setTimeout(ss.next, ss.milliseconds);
}
};
ss.next = function() {
ss.current += 1;
if (ss.current > ss.x.length) {ss.current = 1;}
ss.start();
};
ss.previous = function() {
ss.current -= 1;
if (ss.current < 1) {ss.current = ss.x.length;}
ss.start();
};
ss.display = function (n) {
w3.styleElements(ss.x, "display", "none");
w3.styleElement(ss.x[n - 1], "display", "block");
}
ss.start();
return ss;
};
<html>
<script src="w3.js"></script>
<body>
<div id="2" class="w3-row-padding w3-light-grey w3-padding-64 w3-container">
<div class="w3-content">
<div class="w3-content w3-display-container w3-center">
<div class = "Slide2 w3-animate-opacity">
<h1> Slide Content </h1>
</div>
<!-- Second slide App Development -->
<div class = "Slide2 w3-animate-opacity">
<h1> Slide Content </h1>
</div>
<!-- Third slide App Development -->
<div class = "Slide2 w3-animate-opacity">
<h1> Slide Content </h1>
</div>
<!-- Fourth slide App Development -->
<div class = "Slide2 w3-animate-opacity">
<h1> Slide Content </h1>
</div>
<div class="w3-center w3-container w3-section w3-large w3-text-grey" style="width:100%">
<span class="w3-left w3-hover-text-blue fa fa-arrow-left" onclick="myShow2 .previous()"></span>
<span class="w3-right w3-hover-text-blue fa fa-arrow-right" onclick="myShow2 .next()"></span>
<span class="fa fa-circle demo w3-hover-text-blue w3-transparent" onclick="myShow2 .display(1)"></span>
<span class="fa fa-circle demo w3-hover-text-blue w3-transparent" onclick="myShow2 .display(2)"></span>
<span class="fa fa-circle demo w3-hover-text-blue w3-transparent" onclick="myShow2 .display(3)"></span>
<span class="fa fa-circle demo w3-hover-text-blue w3-transparent" onclick="myShow2 .display(4)"></span>
</div>
</div>
</div>
<script> myShow2 = w3.slideshow(".Slide2", 0); </script>
</div>
</body>
</html>
So the code works well and does what I want except I'm not sure how to make the circle indicators change to blue to reflect the current slide. I have tried completely different approaches except this is the only one that allows multiple slideshows on the same page. Could some please explain to me how I would go about making the circles stay blue when clicked and revert when another is clicked?
I have found a solution to my problem. First I added an extra variable to call a function <script> myShow4 = w3.slideshow(".Slide4", 0, "demo4"); </script>
the "demo4" will be used to find the indicator dots. <span class="fa fa-circle demo4 w3-hover-text-blue w3-transparent" onclick="myShow4 .display(4)"></span>each indicator has the specific class.
`ss.display = function (n) {
ss.current = n;
w3.styleElements(ss.x, "display", "none");
w3.styleElement(ss.x[n - 1], "display", "block");
ss.indicator(demo);
}
ss.indicator = function(n) {
var dots = document.getElementsByClassName(n);
for (i = 0; i < dots.length; i++) {
dots[i].className = dots[i].className.replace(" w3-text-blue", "");
}
x[ss.current-1].style.display = "block";
dots[ss.current-1].className += " w3-text-blue";
}`
This is the new code in the JS file. on click the display function is called and I edited the function to also call the indicator function which changes the current slide relevant dot to blue while changing the rest to nothing or how they were before. This solution worked for me, adding another function within the called function.

how to expand and collapse a mixed list of shown and hidden elements (filtered divs) without showing the hidden ones

I'm basically new to coding but i have this LONG list of products which i need to filter according to certain criteria plus a show more/less function. The issues are: 1. When i filter elements, some categories have less items than the default set, 2. if the shown element's total height is is shorter i need the container's height to match that height (i set a max-height but then it won't increase when the filter changes the number of products or i try to expand the list to show other hidden products) 3. if a filter is applied and the list is longer than the default lenght shown, if i use the function to show more items, the hidden elements that do not belong to that category become visible.
Here is the actual jquery script, the html is so long i'm not posting it complete, but i'll post an example of the product setup.
jQuery script
$(document).ready(function() {
var productList = $("#product_list:nth-child(3n+1):visible").height(); /*$("#product_list:nth-child(3n+1):visible").each(function(){productList += $(this).height();});*/
var counter = 0;
//filter functions
$("ul#filters li a.button").click(function() {
$("ul#filters li a.button").removeClass("active");
$(this).addClass("active");
if ($(this).hasClass("active") == true) {
var act = $(this).attr("id");
$(".product:not(#" + act + ")").fadeOut(800);
$(".product#" + act + "").fadeIn(800);
$(".products .product:nth-child(4n+1)").css("border-left-width", 0);
$("#product_list:last-child:visible").css("border-right", "2px dashed #008140");
if (act == "all") {
$(".product").fadeIn(800);
} else {
if ($(".product:last:visible") >= $(".product:last")) {
//$("#loadmore").css("display", "none");
$(".product:last:visible").css("border-right", "2px dashed #008140");
}
}
}
});
//show more -show less
$("#loadmore").click(function showNext() {
$("#product_list").animate({
height: productList + 3000
}, 800);
$(".product:hidden:lt(20)").slideDown();
$("#showless").css("display", "block");
counter = counter + 20;
productList = productList + 3000;
if ($(".product:last").is(":visible")) {
$("#product_list").css("max-height", (productList - 2300));
$("#product_list:last-child:visible").css("border-right", "2px dashed #008140");
$("#loadmore").css("display", "none");
}
if ($("."))
});
$("#showless").click(function showLess() {
//$(".product:visible:gt("+(counter-1)+")").slideUp()
$("#product_list").animate({
height: productList - 3000
}, 800);
counter = counter - 20;
productList = productList - 3000;
if (productList == 3000) {
$("#showless").css("display", "none");
}
if ($("#loadmore").css("display", "none")) {
$("#loadmore").css("display", "block");
}
});
});
product HTML
<div class="container">
<!-- Row -->
<div class="row" id="product_list">
<!-- Product -->
<div class="col-xs-6 col-sm-3 product" id="belleza">
<!-- Entry -->
<div class="entry">
<figure class="entry-thumbnail">
<img alt="" src="../resources/images/productos/Shampoo-neutro.png"/>
</figure>
<div class="entry-body">
<h3 class="entry-title">
Shampoo Petra Neutro<br/>
</h3>
<h2 class="entry-title">
<select id="presentaciones">
<option id="shampoo-neutro260">Frasco Dosificador x260ml</option>
</select>
</h2>
<h2 class="entry-title" id="disponible">Disponibles: <span class="unidades" id="shampoo-neutro">10</span>
</h2>
<span class="price"><ins><span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">$</span>12.00</span></ins>
<del><span class="woocommerce-Price-amount amount"><span class="woocommerce-Price-currencySymbol">$</span>15.00</span></del></span>
<div class="buttons">
<a class="button add_to_cart_button" rel="nofollow">Add to cart</a>
</div>
</div>
</div>
<!-- End Entry -->
</div>
<!--End Col -->
</div>
</div>

Categories