Modify code to take textContent from li instead of thumbnail img src? - javascript

I found (and tweaked) the code below that was designed for switching the larger img src with the src of thumbnails in a list, but I'm not sure how to adjust it to use something like https://picsum.photos/id/CLICKED_LI_textContent/200/200 as the URL instead of pulling from a thumbnail's src.
For some more context here's the original post in which I was looking into this
How can I change img src from list of (non-image) items?
I haven't taken any JS classes, so I'm not sure how every component of the script works. I'm more comfortable with pure HTML and CSS, but think JS is the answer for making this work more smoothly.
(I did add the jquery script src to the document for this)
Sorry the code is a little ugly, I would have added the script and style tags and such but I ran out of time while posting this
$("#list li").click(function(e) {
// if i use this getting undefined
// var src = $(this).attr("src");
// so i use this method
var target = e.target;
var src = target.src;
console.log(src);
$("#display").fadeOut(function() {
$(this).on('load', function() {
$(this).fadeIn();
});
$(this).attr("src", src);
});
//record which thumb was clicked
$("#list li").removeClass("active"); //remove class
$(this).addClass("active"); //apply class to selected thumb
});
//move next
$("#left-arrow").click(function() {
if ($("#list li.active").next("#list li").length > 0) {
$("#list li.active").next().children( 'img' ).trigger("click");
} else {
$("#list li:first > img").trigger("click"); //go to first
}
return false;
});
//move previous
$("#right-arrow").click(function() {
if ($("#list li.active").prev("#list li").length > 0) {
$("#list li.active").prev().children( 'img' ).trigger("click");
} else {
$("#list li:last > img").trigger("click"); //go to end
}
return false;
});
//click the first thumb to begin
$("#list li:first > img").trigger("click");
.container {
display: flex;
}
.active {
border-bottom: 1px solid #990000;
}
.list {
width: 200px;
cursor: pointer;
padding: 0.25rem;
}
list > li * {
/* so only the li tag can be event.target, and none of it's children */
pointer-events: none;
}
.display {
max-width: 500px;
max-height: 500px;
}
<div class="container">
<div class="list">
<ul id="list">
<li>237</li>
<li>240</li>
<li>100</li>
<li>301</li>
</ul>
$larr; $rarr;
</div>
<div class="show">
<img src="https://picsum.photos/id/237/200/200" class="display" id="display">
</div>
</div>

Here is a pure javascript solution. The only difference is that it lacks the fading between the images.
I tried to write the code as pedagogic as possible, using variables as explanations. The code goes more with your original thread, where you had a bunch of images with different file endings. I gave the image an alt attribute, so you can see the change.
A short explanation:
Use an array for the images.
Create your list through javascript, using the array.
Add click listeners to your #list, where you read the .textContent. I added pointer-events: none; to any children to the li tags so they don't trigger the click listener.
Add click listeners to your prev/next buttons, where you check which index that the currently visible image has in the array (from 0 to 3 in imageArr) and then adds +1 och -1 to that index.
[edit] Added code for updating the CSS.
const listEl = document.getElementById('list');
const imgElement = document.querySelector('.right > img');
let imageArr = ["237.jpg", "240.gif", "100.jpeg", "301.png"]; // 1
let currentImage = '';
document.getElementById('next').addEventListener('click', () => shiftImage(1));
document.getElementById('prev').addEventListener('click', () => shiftImage(-1));
listEl.addEventListener('click', displayImage);
function updateImage(imageName) {
const subfolder = 'images/';
changeActive(imageName, currentImage); /* ADDED in EDIT */
currentImage = imageName;
imgElement.src = subfolder + imageName;
imgElement.alt = imageName;
}
/* ADDED in EDIT */
function changeActive(newImage, oldImage) {
if (oldImage) {
let oldIndex = imageArr.indexOf(oldImage);
toggleActiveClass(oldIndex);
}
let currentIndex = imageArr.indexOf(newImage);
toggleActiveClass(currentIndex);
}
/* ADDED in EDIT */
function toggleActiveClass(imageIndex) {
let liElements = listEl.childNodes;
liElements[imageIndex].classList.toggle('active');
}
function shiftImage(direction) {
let currentIndex = imageArr.indexOf(currentImage);
let newIndex = currentIndex + direction;
if (newIndex < 0) { newIndex = imageArr.length - 1; }
else if (newIndex >= imageArr.length) { newIndex = 0; }
let newImageName = imageArr[newIndex];
updateImage(newImageName);
}
function displayImage(event) {
let liElement = event.target;
updateImage(liElement.textContent);
}
function constructImageLinks() { // 2
let htmlOutput = '';
for (let imageSrc of imageArr) {
htmlOutput += `<li>${imageSrc}</li>`;
}
listEl.innerHTML = htmlOutput;
}
constructImageLinks();
updateImage(imageArr[0]);
section {
display: flex;
}
section ul {
margin-top: 0px;
}
section > .left li {
cursor: pointer;
padding: 0.25rem;
}
section > .left li.active {
background-color: pink;
}
section > .left li > * {
pointer-events: none;
}
section > div {
padding: 1rem;
}
section > .right > img {
width: 200px;
border: 1px solid;
padding: 0.5rem;
}
<section>
<div class="left">
<ul id="list"></ul>
<button id="prev">Previous</button>
<button id="next">Next</button>
</div>
<div class="right">
<img>
</div>
</section>

JSFiddle - Link
HTML
<div class="container">
<div class="list">
<ul id="list">
<li>237</li>
<li>240</li>
<li>100</li>
<li>301</li>
</ul>
$larr; $rarr;
</div>
<div class="show">
<img src="https://picsum.photos/id/240/200/200" class="display" id="display">
</div>
</div>
Javascript
// HELPER FUNCTIONS
function getImageURL(id, width=200, height=200){
return "https://picsum.photos/id/"+ id + "/" + width + "/" + height;
}
function removeActive(){
let lis = $('#list li');
for(let i=0;i<lis.length;i++){
$(lis[i]).removeClass('active');
}
}
// HANDLE EVENTS
$(document).on('click', "#list li", async (e)=>{
await $('#display').fadeOut();
removeActive();
let li = $(e.target);
li.addClass('active');
let image_id = parseInt($(e.target).html());
let image_url = getImageURL(image_id);
$('#display').attr('src', image_url);
await $('#display').fadeIn();
});
//move next
$(document).on('click', "#left-arrow", (e)=>{
// Handler Here
});
//move previous
$(document).on('click', "#right-arrow", (e)=>{
// Handler Here
});
CSS
.active {
border-bottom: 1px solid #990000;
}
.list {
width: 200px;
cursor: pointer;
padding: 0.25rem;
}
list > li * {
/* so only the li tag can be event.target, and none of it's children */
pointer-events: none;
}
.display {
max-width: 500px;
max-height: 500px;
}
.container {
display: flex;
}

Related

Hide an expanded menu when clicking outside of the container: how to use code snipet

I have a menu that open a sub-menu section onclick (let's name the container: "sub-menu").
I would like "sub-menu" to disapear if the user click outside of it / on the rest of the page.
It seems to be solved on How do I detect a click outside an element?
But I can't get how to use the code snipet from the second most popular answer:
export function hideOnClickOutside(selector) {
const outsideClickListener = (event) => {
const $target = $(event.target);
if (!$target.closest(selector).length && $(selector).is(':visible')) {
$(selector).hide();
removeClickListener();
}
}
const removeClickListener = () => {
document.removeEventListener('click', outsideClickListener)
}
document.addEventListener('click', outsideClickListener)
}
Could you please guide me on how to use it?
I edited, and included a basic example. -> I want sub menu to also close when clicking on the "white" space. But not on the parent "main menu" element.
document.getElementById("main-menu").addEventListener("click", function() {bouttonexpand('sub-menu-class')});
function bouttonexpand(id) {
var elemeacacher = document.getElementsByClassName(id);
if (elemeacacher[0].style.display != "none"){
for(var y=0;y<elemeacacher.length;y++)
elemeacacher[y].style.display = "none";
}
else {
for(var y=0;y<elemeacacher.length;y++)
elemeacacher[y].style.display = "block";
}
}
#main-menu {
display:inline-block;
height:20px;
width:100px;
background: blue;
padding: 5%;
}
#sub-menu {
display:inline-block;
height:50px;
width:50px;
background: red;
display: none;
}
<div><div id="main-menu">Main menu</div></div>
<div><div id="sub-menu" class="sub-menu-class">Sub menu</div></div>
Thanks
By using jQuery, you can bind to the document click event and hides the div container when the clicked element isn’t the container itself or descendant of the div element.
var container = $("#sub-menu");
if (!container.is(event.target) && !container.has(event.target).length) {
container.hide();
}
If you want to hide that container without being tested the container itself or descendant of the div element just remove the condition and simply use container.hide();.
Also, rather than setting display: none; on sub-menu in the CSS, set it manually so that you can toggle the sub-menu from the very first click.
Have a look at the snippet below:
var x = document.getElementById("sub-menu");
x.style.display = "none";
$(document).click(function (evt) {
if ($(evt.target).is('#main-menu')) { // control click event if it's main-menu
if (x.style.display === "none") {
x.style.display = "block";
} else {
x.style.display = "none";
}
}
else {
var container = $("#sub-menu");
if (!container.is(event.target) && !container.has(event.target).length) { // if you don't want that remove the condition and write container.hide(); only
container.hide();
}
}
});
#main-menu {
display: inline-block;
height: 20px;
width: 100px;
background: blue;
padding: 5%;
}
#sub-menu {
display: inline-block;
height: 50px;
width: 50px;
background: red;
}
<script src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<div id="main-menu">Main menu</div>
<div id="sub-menu" class="sub-menu-class">Sub menu</div>

js vertical slider with arrows

The thing is that I need to make a vertical images slider,so that when i press arrow down/arrow up every image changes it's position (the highest one goes bottom,the previous take it's place)
what it should look like:
what i have got so far:
$(function(){
var $vsliderboxes = $('#vsliderboxes'),
$vslidernav = $('#vslidernav'),
boxHeight = $vsliderboxes.height(),
current_index = 0;
function clickslide(){
clearInterval(intervalTimer);
clearTimeout(timeoutTimer);
timeoutTimer = setTimeout(function () {
intervalTimer = window.setInterval(autoslide, 2000);
}, 2500);
var index = $(this).index();
current_index = index;
$vsliderboxes.children().stop().animate({
top : (boxHeight * index * -1)
}, 500);
}
function autoslide(){
current_index++;
if (current_index >= $vsliderboxes.children().children().length) {
current_index = 0;
}
$vslidernav.find('a').eq(current_index).trigger('click');
}
$vslidernav.find('a').click(clickslide);
var intervalTimer = window.setInterval(autoslide, 2000),
timeoutTimer = null;
});
#vslidernav ul {
list-style: none;
padding: 0;
}
#vslidernav ul a {
padding: 0;
cursor: pointer;
height: 50px;
}
#vslidernav ul a:active {
color: #9C9A99;
}
#vslidernav ul a li {
height: 50px;
}
#vslidernav ul .active li {
}
.#vslidernav ul a:active {
background: transparent;
color: #9C9A99;
}
.vslider {
display: inline-block;
}
#vslidernav {
float: left;
width: 100px;
z-index: 1;
height: 250px;
}
#vsliderboxes {
position : relative;
overflow : hidden;
}
#vsliderboxes div {
height: 250px;
width: 900px;
}
#vsliderboxs-inner {
position : relative;
width : 900px;
height : 250px;
}
<div class="vslider">
<div id="vslidernav">
<ul>
<a id="1">
<li><img src="img/arrtop.gif"></li>
</a>
<a id="2">
<li><img src="img/arrdown.gif"></li>
</a>
<a id="3">
<li></li>
</a>
</ul>
</div>
<div id="vsliderboxes">
<div id="vsliderboxs-inner">
<div id="box1" class="active"><img src="img/slide1.gif"></div>
<div id="box2" class="inactive"><img src="img/slide2.gif"></div>
<div id="box3" class="inactive"><img src="img/slide3.gif"></div>
</div>
</div>
</div>
thanks for any advice
I think, that it isn't possible to solve this issue like you try to.
Because, when you work with the "top" property, you can't take one image from the top and append it to the other end because appending the image, will move the other images to another place --> the top property wouldn't be correct any more.
I think the contributed sliders (e.g. http://www.jssor.com/demos/vertical-slider.slider) work with the transform CSS property.
transform: translate3d()
Try to research about this property.
Roko C. Buljan answered on this page: loop carousel jquery
He uses a scrollTop loop for your problem.
I've also written a simple slider some time ago. I have now implemented the Roku C. Buljan method. Feel free to look at my code on Bitbucket.
https://bitbucket.org/d-stone/jqueryslider
An excerpt may help you:
value = prev_or_next == 'next' ? self.globals.slide_height : 0;
last = $('#container').find('> div:last');
first = $('#container').find('> div:first');
if(prev_or_next == 'prev') { // click on "next"-button
first.before(last); // put last element before first
settings.elements.inner.scrollTop(self.globals.slide_height); // set the scrollTop to 1 slide-height
}
// animation itself:
$('#container').stop().animate({scrollTop: value}, {
duration: settings.slide_speed,
done: function() {
if(prev_or_next == 'next') {
// put first item after last
last.after(first);
}
}
});
I'd advise you to validate your HTML (W3C Validator). There are some errors inside.
Invalid HTML can be the reason for some CSS and Javascript Errors.

How to toggle class "open", "closed" on an array in vanilla javascript

To get a better idea what i'm doing look here for my previous code that i try to make a little better >>Codepen
I want to have an array that i fill up with all the id's that i try to animate and with one function toggle the classes .open .closed on every id in the array.
so on an click add .open to #Hamburger, #Navigation, #Black-filter. and one second click remove .open and add .closed for those id's.
because i'm still learning javascript i want it to work in vanilla javascript so i understand the basics before im going on with jquery.
var hamburger = document.getElementById('Hamburger');
var navigation = document.getElementById('Navigation');
var blackFilter = document.getElementById('Black-filter');
var isOpen = true; // true or false
var animation = [h, s, b]; // #H #S #B
var open = "open"; // .open
var closed = "closed"; // .closed
function trigger() {
if (isOpen === true) {
animation.classList.add(open); // add .open to all id's
animation.classList.remove(closed); // remove .closed from all id's
} else {
animation.classList.add(closed);
animation.classList.remove(open);
}
isOpen = !isOpen; // toggles true to false
}
hamburger.addEventListener('click', trigger, false); // onclick toggle class
blackFilter.addEventListener('click', trigger, false); // onclick toggle class
body {
width: 100%;
}
#Hamburger {
height: 100px;
background: red;
width: 100px;
}
#Hamburger.open {
opacity: 0.5;
}
#Hamburger.closed {
opacity: 1;
}
#Navigation {
height: 100px;
background: blue;
width: 100px;
}
#Navigation.open {
opacity: 0.5;
}
#Navigation.closed {
opacity: 1;
}
#Black-filter {
height: 100px;
background: green;
width: 100px;
}
#Black-filter.open {
opacity: 0.5;
}
#Black-filter.closed {
opacity: 1;
}
<body>
<div id="Hamburger"></div>
<div id="Navigation"></div>
<div id="Black-filter"></div>
</body>
What you are looking for is:
var isOpen = true;
var hamburger = document.getElementById('Hamburger');
var navigation = document.getElementById('Navigation');
var blackFilter = document.getElementById('Black-filter');
var animatable = [hamburger, navigation, blackFilter];
var openClass = "open"; // .open
var closedClass = "closed"; // .closed
function trigger() {
if (isOpen) {
animatable.forEach(function (element) {
element.classList.add(openClass);
element.classList.remove(closedClass);
});
} else {
animatable.forEach(function (element) {
element.classList.add(closedClass);
element.classList.remove(openClass);
});
}
isOpen = !isOpen;
}
hamburger.addEventListener('click', trigger, false);
blackFilter.addEventListener('click', trigger, false);
Demo
There are a few things that need improvement.
First of all you are naming you variables rather poorly. Which is actually already one of your problems, first you say that
var b = document.getElementById('B');
and then later
var b = "closed";
So this needs to be fixed, use variable names that are descriptive so you will know what you are talking about when.
Last but not least you are trying to change the elements of that array a, not the array itself. So you need to access the elements by themselves, set their classes and then you are good to go e.g.:
for( var index in a ) {
if ( open === true ) {
a[index].classList.add(b);
a[index].classList.remove(c);
} else {
a[index].classList.add(c);
a[index].classList.remove(b);
}
open = !open;
Firstly ou don't need "open" AND "close" classes, only one would clearly simplify your code (and there is the "default" state).
Then, add a class for all your buttons, the easily manipulate them in JS and CSS (here the class ".btn");
// Directly get on array (a NodeList more precisely)
var buttons = document.getElementsByClassName('btn');
function toggleClass() {
// Loop to add or remove (toggle) the the '.open' class
for (var i=0; i<buttons.length; i++) {
buttons[i].classList.toggle('open');
}
}
// Loop to add event listener to all buttons
for (var i=0; i<buttons.length; i++) {
buttons[i].addEventListener('click', toggleClass, false);
}
.btn {
height: 100px;
width: 100px;
}
.btn.open {
opacity: 0.5;
}
#Hamburger { background: red; }
#Navigation { background: blue; }
#Black-filter { background: green; }
<div id="Hamburger" class="btn"></div>
<div id="Navigation" class="btn"></div>
<div id="Black-filter" class="btn"></div>
This is already way simpler. But you should have a parent element holding the opened/closes state, so you wouldn't loop in an array.
// Only need to manipulate one DOM node
var menu = document.getElementById('menu');
function toggleClass() {
menu.classList.toggle('open');
}
menu.addEventListener('click', toggleClass, false);
body {
width: 100%;
}
.btn {
height: 100px;
width: 100px;
}
.menu.open > .btn {
opacity: 0.5;
}
#Hamburger { background: red; }
#Navigation { background: blue; }
#Black-filter { background: green; }
<div class="menu" id="menu">
<div id="Hamburger" class="btn"></div>
<div id="Navigation" class="btn"></div>
<div id="Black-filter" class="btn"
</div>
Your event listener gets the event as the 1st argument. Use it to decide what to do:
function trigger(event) {// use event.target ... }

.is() not working as intended

I'm new to jQuery, practicing with a slideshow and trying to make it repeat. For some reason if ( last.is(active) ) is never returning true. In inspector I can see that the last child is getting the active ID. Is this not how it's meant to be used? if (active != last) didn't work either.
Fiddle
JS
function changeImage() {
var active = $('#active');
var first = $('#slideshow:nth-child(1)');
var last = $('#slideshow:nth-last-child(1)');
if ( last.is(active) ) {
var next = first;
} else {
var next = active.next();
}
next.attr('id', 'active');
active.attr('id', 'inactive');
}
setInterval(changeImage, 1000);
Markup
<div id="slideshow">
<img id="active" src="https://i.imgur.com/8iOuvtf.jpg">
<img src="https://i.imgur.com/Wi8mlH2.jpg">
<img src="https://i.imgur.com/MNhgqco.jpg">
</div>
CSS
#slideshow img {display: none;}
#slideshow #active {
display: block;
width: 100%;
height: auto;
}
I put this fiddle together for you.
https://jsfiddle.net/76xg0d7L/
It is much easier to work with classes than IDs in this situation. I have modified your changeImage() function.
function changeImage() {
var $next = $('.slide.active').removeClass('active').next('.slide');
if ($next.length) {
$next.addClass('active');
}
else {
$('.slide:first').addClass('active');
}
}
setInterval(changeImage, 1000);
I've also update your CSS to select the active class, as opposed to the ID.
#slideshow img {display: none;}
#slideshow .active {
display: block;
width: 100%;
height: auto;
}
Let me know if you have any questions or if this works for you.
You should use it more like
function changeImage() {
var active = $("[state='active']");
var next = active.is(":last") ? $("#slideshow:first-child") : active.next();
active.data("state", "inactive"); //don't multiply same id attribute
next.data("state", "active");
}
setInterval(changeImage, 1000);
https://jsfiddle.net/gm9twq6j/
You should pass around a class called "active" using .addClass, .removeClass, and .hasClass instead of what you are trying to do with swapping out IDs.
JS:
function changeImage() {
var active = $('#slideshow').find('.active');
var first = $('#slideshow img:first-child');
var last = $('#slideshow img:last-child');
if ( last.hasClass('active') ) {
var next = first;
} else {
var next = active.next();
}
active.removeClass('active');
next.addClass('active');
}
setInterval(changeImage, 1000);
CSS:
#slideshow img {display: none;}
#slideshow .active {
display: block;
width: 100%;
height: auto;
}
HTML:
<div id="slideshow">
<img class="active" src="https://i.imgur.com/8iOuvtf.jpg">
<img src="https://i.imgur.com/Wi8mlH2.jpg">
<img src="https://i.imgur.com/MNhgqco.jpg">
</div>

How to animate the list?

This is my JSFiddle
As you can see from the fiddle that there is a list that is being scrolled with the help of arrows.. So what I want is to animate that transition when the list visible and hidden.
I don't know about the animation. I have seen many examples and tried to adjust them with my example but it's not working... How do I get the list to animate?
$(document).ready(function(){
var code='';
for(var i=1;i<=20;i++)
{
code+="<li>list Item "+i+"</li>";
}
$('#list-items').html(code);
});
var list_items = [];
var index = 0;
var list_length = 0;
function getAllListItems() {
var temp = document.getElementsByTagName('li');
for (i = 0; i < temp.length; i++) {
list_items.push(temp[i]);
}
list_length = temp.length;
}
getAllListItems();
function move(dir) {
if (dir == left) {
list_items[index].style.display = 'block';
index--;
if (index < 0) {
index = 0;
}
} else if (dir == right) {
list_items[index].style.display = 'none';
if (index >= ((list_length) - 1)) {
index = (list_length) - 1;
} else {
index++;
}
} else {}
}
* {
box-sizing: border-box;
}
ul {
float:left;
height:50px;
width: 600px;
overflow: hidden;
}
ul li {
text-align: center;
border: 2px solid black;
width: 100px;
height: 50px;
float: left;
list-style-type: none;
background-color: aquamarine;
}
ul li:first-child {
display: block;
}
#left, #right {
float:left;
height:50px;
background-color:aqua;
font-size:2em;
padding-left: 20px;
padding-right:20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body onload='getAllListItems()'>
<div id='t'></div>
<button id='left' onClick="move(left)">
<</button>
<ul id='list-items'>
</ul>
<button id='right' onClick='move(right)'>></button>
</body>
You can easily just replace your lines:
list_items[index].style.display = 'block';
list_items[index].style.display = 'none';
with the jQuery show() and hide() functions:
$(list_items[index]).show("slow");
$(list_items[index]).hide("slow");
As demonstrated in my updated version of your Fiddle
For different transitions, you can use the animate() function, which lets you tell it what css properties to affect. In addition to numeric values, jQuery also supports the special values 'show', 'hide', and 'toggle' (which, incidentally, will show, hide, or toggle the show/hide status of an element using that property). So for instance, if you wanted to shrink them only horizontally and leave the vertical alone, you could change the .show() and .hide() calls to:
$(list_items[index]).animate({width:'show'}, 600);
$(list_items[index]).animate({width:'hide'}, 600);
I've demonstrated this in another updated Fiddle

Categories

Resources