Switch between multiple (5-7) images on click and loop - javascript

I want to have a really simple gallery of around 5-7 images, where you can click on the image and it switches to the next one, and by the end it circles back to the beginning. My code right now only allows me to have 3 images, where it just loops between image 2 and image 3.
function swaparrows(obj, i1, i2, i3) {
var src = obj.getAttribute('src');
if (src.match(i1))
obj.setAttribute('src', i2);
else
obj.setAttribute('src', i1);
}
And for the HTML:
<img src="https://minecraft-statistic.net/en/og/player/Druio.png"
onclick="swaparrows(this, 'https://camblab.info/wp/wp-content/uploads/2017/02/pool-water.jpg', 'https://i2-prod.mirror.co.uk/incoming/article13348246.ece/ALTERNATES/s615/0_SupernumeraryRainbows_Entwistle_13621-610x859.jp', 'https://minecraft-statistic.net/en/og/player/Druio.png')" />

You can use an attribute on the element to figure out what the current index is. Then, increment that index by one and cap it off at the end of the array via the modulus operator so that it cycles circularly. Also, please do not use onclick for such long code snippets because it makes code very unmaintainable. Also, if you are not using jQuery, then apply to the whole page so that you can easily and quickly add new interactive content. If you are using jQuery, then do not do this because your page will get very laggy very quickly because jQuery embodies and promotes poor performance.
🢔 Because it appears as though you are new to Stackoverflow, I shall explain this checkmark here. After reading and reviewing my answer, if (and only if) you are thoroughly satisfied with the answer I have posted here, then you can reward me by clicking this checkmark to accept this answer as the best answer. If someone else posts a better answer, then click their checkmark. Clicking a checkmark is not permanent: if someone later on post a better answer, then you can click their checkmark and switch the best answer over to the new answer posted.
<img src="https://minecraft-statistic.net/en/og/player/Druio.png"
data-swap-index="2"
data-swap-0="https://camblab.info/wp/wp-content/uploads/2017/02/pool-water.jpg"
data-swap-1="https://i2-prod.mirror.co.uk/incoming/article13348246.ece/ALTERNATES/s615/0_SupernumeraryRainbows_Entwistle_13621-610x859.jpg"
data-swap-2="https://minecraft-statistic.net/en/og/player/Druio.png"
style="max-height:80vh" decoding="async" />
<img src="https://www.minecraft.net/content/dam/archive/og-image/minecraft-hero-og.jpg"
data-swap-index="0"
data-swap-0="https://www.minecraft.net/content/dam/archive/og-image/minecraft-hero-og.jpg"
data-swap-1="https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/10/29/2f/10292fd0-d87a-6856-7523-f67fa8051df7/AppIcon-0-1x_U007emarketing-0-85-220-9.png/246x0w.jpg"
style="max-height:80vh" decoding="async" />
<script>
addEventListener("click", function(evt){
// when anywhere on the page is clicked, this will fire
var eventObj = evt || window.event;
var target = eventObj.target;
var swapIndex = target.getAttribute("data-swap-index");
if (swapIndex !== null) {
var newSwapIndex = (swapIndex|0) + 1|0;
if (!target.hasAttribute("data-swap-" + newSwapIndex)) {
// cycle back to the first image
newSwapIndex = 0;
}
// now apply the new swap index
target.setAttribute(
"src",
target.getAttribute("data-swap-" + newSwapIndex)
);
target.setAttribute("data-swap-index", newSwapIndex);
eventObj.preventDefault(); // prevent link from opening or something
} // else the element being clicked on is not swappable
});
</script>
Notice how I prefix the attributes with data. All attributes prefixed with data- have been set aside for special custom use purposes by the WhatWG with datasets, thus it is always a good idea to prefix custom attributes with data- in order to ensure that in the future the attribute the you use is not added to the specification with special effects based upon its value.

Related

Parse a JS array and add a style for matching values using .addClass()

I'm confused as I don't believe I've done anything out of the ordinary here, that I haven't done before.
I'm looking to implement a feature similar to Excel's "Highlight Duplicate Values" action.
I have a function that takes tab separated values and parses them into a list of claims. It's all hacky, but the "list" (or table) is a series of divs that contains various elements such as a button that holds the claim ID. Once the list is created and all claims are visible, I parse it to find what buttons hold duplicate values, and apply a class to said button to highlight it using .addClass().
The problem is that I cannot seem to get the button to highlight. Now I've used functions like this before without any issue, including making a row highlight when clicked (and dim the others), and so on, but I'm confused what seems to be wrong here.
The parsing code is as follows:
function parseForDuplicates() { // Look for duplicates to highlight
var importedListLength = $("div.column.colClaim").length; // Get length of claims in list
var claimsToReview = []; // create an array for below...
for (let a=0; a < softParsedList.length; a++) { // Add the claim IDs to the claimsToReview array
claimsToReview.push(softParsedList[a][0]);
}
for (let i=0; i < importedListLength; i++) { // Compare values...
var currentCheckedClaimBtn = $(`div.column.colClaim:nth-child(${i+1}) button`); // Get the button element for current line
if (i > 0) {
var previousCheckedClaimBtn = $(`div.column.colClaim:nth-child(${i}) button`); // Get the button element for previous line only if we're further along the list
} else {
var previousCheckedClaimBtn = "none"; // set to "none" if we're at the beginning.
}
if (i < importedListLength) {
var nextCheckedClaimBtn = $(`div.column.colClaim:nth-child(${i+2}) button`); // Get button element from next line
} else if (i == importedListLength) {
var nextCheckedClaimBtn = "none"; // set to "none" if we're at the end
}
var currentCheckedClaim = $(`div.column.colClaim:nth-child(${i+1}) button`).val(); // Get claim ID from current line
if (i > 0) {
var previousCheckedClaim = $(`div.column.colClaim:nth-child(${i}) button`).val(); // Get claim ID from previous line
} else {
var previousCheckedClaim = "none"; // set to "none" if we're at the beginning
}
if (i < importedListLength) {
var nextCheckedClaim = $(`div.column.colClaim:nth-child(${i+2}) button`).val(); // Get claim ID from next line
} else if (i == importedListLength) {
var nextCheckedClaim = "none"; // set to "none" if we're at the end
}
//console.log(`Previous Claim: ${previousCheckedClaim}`);
//console.log(`Current Claim: ${currentCheckedClaim}`);
//console.log(`Next Claim: ${nextCheckedClaim}`);
if (currentCheckedClaim == nextCheckedClaim) { // If the current claim matches next claim
currentCheckedClaimBtn.addClass('duplicateClaim'); // Highlight current button
nextCheckedClaimBtn.addClass('duplicateClaim'); // Highlight next button
nextCheckedClaimBtn.addClass('duplicateClaim'); // Highlight next button
console.log(`${currentCheckedClaim} is a duplicate!`);
}
if (currentCheckedClaim == previousCheckedClaim) { // If the current claim matches previous claim
currentCheckedClaimBtn.addClass('duplicateClaim'); // Highlight current button
previousCheckedClaimBtn.addClass('duplicateClaim'); // Highlight previous button
console.log(`${currentCheckedClaim} is a duplicate!`);
}
}
}
The CSS in question...
.duplicateClaim {
border-color: #BB9955 !important;
background-color: #773311 !important;
color: #FFFFFF !important;
}
I've tried with and without !important and that doesn't change anything
When I open up DevTools I can see that the styles get applied appropriately, however in the Styles sidebar, the style is nowhere to be found! I can add it manually to the stylesheet, and at that point, the buttons highlight as intended!
I've gone over the seemingly trivial things: I've definitely saved the CSS file. The HTML file points to the correct CSS file (it never changed to begin with), and there are no conflicting styles that I'm aware of. I use two stylesheets (one named style2.css and the other named external.css and there are no conflicts between them. I tried disabling external.css - no change. I also made sure I didn't somehow open the wrong css (from production or a backup) and just have been editing CSS that was never referenced by changing a DIFFERENT rule - the body - to have a background color that's #FFFFFF and it works. I've also tried inserting it directly into the HTML... that doesn't work. Moving .duplicateClaim to the TOP of the CSS file? Nope. Maybe I just fat fingered it all? Nope. Spelling is correct. I did a copy/paste just in case my eyes have betrayed my brain - nah, they're still cooperating with me... for now. And what about adding a class that's actually an ID?
Yeah, no, that period has not magically turned into a hash. Most definitely has remained a period, and hasn't given me an issue. Oh oh! But what about applying the style DIRECTLY too the element? Nah, I assure you I've tried and nothing wants to play nice.
And lastly, I've tried generating each line WITH the class already entered! That doesn't work either. For whatever reason, chrome just seems to eat that one rule for lunch, and it just doesn't exist. I feel like there is SOMETHING dumb that I'm just not looking at and I'm just dealing with being really, really tired, but I need some kind of sanity check here. Or, maybe I really am going crazy...
Of course, again, let me emphasize that when looking at the source in the developer tools, the class most definitely gets added to the elements that I specify in the code - the JS appears to work, regardless of whether or not it's messy and triggers you (however it's not the first time something appears to work but is still wrong. It just seems like the class ceases to exist the moment the page is loaded. And if I manually add it in the developer tools by clicking the New Style Rule button (the plus next to the :hov and .cls buttons)? It applies to the correct buttons without issue.

Next/Prev buttons to step through div contents

First of all a disclaimer, I'm not a dev. I'm halfway through The Odin Project and have covered some HTML and CSS, but, have not yet started on JS. In order to help with my learning I've created my own blog. My aim is for each blog post to have its own stylesheet (so with each new post I learn a little more about CSS).
Anyway, I plan to write a post about the benefits of using an eReader, specifically the Kindle. I've styled the page to look like a Kindle Oasis, and I'd like the reader to be able to step through the article contents via the Kindle's next/prev buttons, but, as I'm not a dev, this is where I'm stuck. Via Stack overflow I've managed to add some JS that will display page 1, 2 and 3 via dedicated buttons for each dive element, but, what I really need is to step through x number of pages via the prev/next buttons.
Here's what I have so far: https://codepen.io/dbssticky/pen/yLVoORO. Any help would be much appreciated. What I should do of course is finish The Odin Project and come up with a solution on my own, but, I'd really like to get this Kindle article published sooner rather than later. Hence my rather cheeky request for assistance.
Here's the JS I'm currently using:
function swapContent(id) {
const main = document.getElementById("main_place");
const div = document.getElementById(id);
const clone = div.cloneNode(true);
while (main.firstChild) main.firstChild.remove();
main.appendChild(clone);
}
You have the right idea and it just needs a few adjustments to get the previous/next functionality.
Currently your div IDs are following the format operation1, operation2, and so on. Since you want the previous/next functionality you'll need to change your 'swapping' function, which currently takes the full ID, to use the numeric portion only.
Add a new function which appends the number to 'operation' instead of using the whole thing:
function goToPage(pageNumber){
const main = document.getElementById("main_place");
const div = document.getElementById("operation" + pageNumber);
const clone = div.cloneNode(true);
while (main.firstChild) main.firstChild.remove();
main.appendChild(clone);
}
And then change your Page 1/2/3 buttons to use goToPage(1), goToPage(2) and so on.
Now for the previous/next functionality you'll need a way to track which page you're on, so that you can figure out which page to load.
Add a variable at the top (outside functions)
var currentPage = 0;
Then add a line in your goToPage function to track the page you're on.
currentPage = pageNumber;
Now that you're tracking you can add a previous and next function.
function goNextPage(){
goToPage(currentPage-1);
}
function goPreviousPage(){
goToPage(currentPage+1);
}
Then call it from the previous and next buttons.
<button onClick="goNextPage()" class="next-button"></button>
<button onClick="goPreviousPage()" class="previous-button"></button>
Here's a codepen: https://codepen.io/srirachapen/pen/WNZOXQZ
It's barebones and you may have to handle things like non existent div IDs.
HTML
<button class="next-button" onclick="nextContent()"></button>
<button class="previous-button" onclick="prevContent()"></button>
JS
var pageid = 0;
var maxpage = 3;
function nextContent() {
if(pageid == maxpage) return
pageid++
swapContent(`operation${pageid}`)
}
function prevContent() {
if(pageid == 1) return
pageid--
swapContent(`operation${pageid}`)
}
you can try this to switch between pages. But you may need to edit the "swapContent" method more sensibly.
Track the Current Page
Whatever solution you use to render pages & links (manual hardcoded links & content vs externally-stored & auto-generated), one thing is unavoidable: You need to track the current page!
var currentPage = 0
Then, any time there's a page change event, you update that variable.
With the current page being tracked, you can now perform operations relative to it (e.g. +1 or -1)
I'd suggest making a goToPage(page) function that does high-level paging logic, and keep your swapContent() function specifically for the literal act of swapping div content. In the future, you may find you'd want to use swapContent() for non-page content, like showing a "Welcome" or "Help" screen.
Example:
function goToPage(page) {
// Update `currentPage`
currentPage = page
// ... other logic, like a tracking event or anything else you want you occur when pages change
// Do the actual content swap, which could be your existing swapContent()
swapContent('operation'+page)
}
You'd invoke the function like so:
goToPage(3) // Jump to a specific page
goToPage(currentPage + 1) // Go to the next page
goToPage(currentPage - 1) // Go to the prev page
You can make separate helper functions like "goToNextPage()" if you desire, but for sure you start with a fundamental page-change function first.

Text not changing in jQuery

I seem to be doing something wrong in the following code: http://jsfiddle.net/yunowork/qKj6b/1/
When you click next, the text within the span .hiddentext should be displayed in the span .showtext on top and correspond to the right Race (Rn). For example when R3 is highlighted the content of that .hiddentext "Race 3Oregon 14:30" should be displayed within the span .showtext.
This is the line where I make a mistake:
$('.showtext').text($('.hiddentext').first('td:first').text());
What am I doing wrong here?
Let's start simple:
Your problem:
$('.showtext').text($('.hiddentext').first('td:first').text());
you are saing, that, grab all .hiddentext, choose the first that has a td ... witch is not what you have in code, you have, td that contains hiddentext... so, the other way around.
What you want to do is simply get the current NEXT td and grab the hiddentext, so, just change to:
$('.showtext').text($nextCol.find('.hiddentext').text());
Now, can you see that the <br/> is not correctly rendered? That's because you are setting the text property, and you should set the html property.
the final code should be something like:
$('.showtext').html($nextCol.find('.hiddentext').html());
live example: http://jsfiddle.net/qKj6b/8/
Your code:
every time you need to have placeholders to provide some data to a context, please, DO NOT USE HTML TAGS to hold such values and hide them... make the use of the data- attribute, witch is a HTML5 complience, and works very well in any browser even if it does not have not HTML5 support, like IE6.
your table definition (td) that currently is:
<td class="visible" id="r2">
<span class="hiddentext">Race 2<br />Santa Fe 12:00</span>
<strong>R2</strong>
</td>
should be something like:
<td class="visible" id="r2" data-text="Race 2<br />Santa Fe 12:00">
R2
</td>
witch is way easier to read, and from your javascript code, you can easily get this as:
var hiddenText = $nextCol.data("text");
Your code (part 2):
This one is quite simple to know
Every time you are repeating yourself, you're doing it wrong
You have the methods for Next and Prev almost exactly as each other, so, you are repeating everything, for this, you should refactor your code and just use one simple method, this way, any future change only happens in one place, and one place only.
$(".next").click(function(e){
e.preventDefault();
var $nextCol = $('.highlighted').next('td');
MoveCursor($nextCol, 'next');
});
$(".previous").click(function(e){
e.preventDefault();
var $prevCol = $('.highlighted').prev('td');
MoveCursor($prevCol, 'prev');
});
function MoveCursor(col, side) {
var maxCol = 8;
if((side === 'next' && col.length != 0) ||
(side == 'prev' && col.length != 0 && col.index() >= maxCol)) {
$('.highlighted').removeClass("highlighted");
col.addClass("highlighted");
// show current title
$('.showtext').html(col.data('text'));
if (col.hasClass("invisible")) {
col.removeClass("invisible");
col.addClass("visible");
var $toRem;
if(side == 'prev')
$toRem = col.next('td').next('td').next('td').next('td').next('td').next('td');
else
$toRem = $nextCol.prev('td').prev('td').prev('td').prev('td').prev('td').prev('td');
$toRem.removeClass("visible");
$toRem.addClass("invisible");
}
}
}
Live Example: http://jsfiddle.net/qKj6b/22/
It should be
$('.showtext').html($('.highlighted .hiddentext').html());
Similar for the prev link...
or even better, thanks to #balexandre:
$('.showtext').html($nextCol.find('.hiddentext').html());
$('.showtext').html($prevCol.find('.hiddentext').html());
Fiddle
Update to match #balexandre hint: Fiddle 2
Do the following:
var $currCol = $('.highlighted'); //to get the current column
$('.race strong').text($currCol.closest('.highlighted').first('td:first').text());
.hiddentext class selects all the spans and the first() will always return you the first td.
Just make sure you select .hiddentext from the currently highlighted column and you are good to go.
$('.showtext').text($('.highlighted .hiddentext').first('td:first').text());
Try this (Same for both)
$('.showtext').html($currCol.find('span.hiddentext').html());
Working Example.

Random color border (Javascript) around database entries (thumbnails)

at the moment I'm working on a website that is meant to be my portfolio so I wanted it to be a challenge.
The section where I show my work is coded with PHP and is connected to a database. With a WHILE loop it adds all the database records on my website.
For this site I have decided to use Javascript for the first time, to make it more challenging and to learn this as well.
What I want is a border around every database record the PHP WHILE loop adds, which is only shown when hovered over and changes color (fixed array of colors) every time you hover over the thumbnail.
This is the code I have so far:
function loaded() {
var colors = ["#FC3E6B","#24FF00","#0087F9","#F9F900"];
var images = document.getElementById("thumbnails").getElementsByTagName("div");
console.log(images);
for (var i = 0; i < images.length; i++) {
var rand = Math.floor(Math.random()*colors.length);
images[i].style.borderStyle = 'solid';
images[i].style.borderWidth = '1px';
images[i].style.borderColor = 'transparent';
$(images[i]).hover(function(){
console.log('hovering over');
images[i].style.borderColor = colors[rand];
}, function(){
console.log('hovering out');
images[i].style.borderColor = 'transparent';
});
};
};
My problem now is that it doesn't work, or partially. This code only applies on the first entry the WHILE loop adds. In the console I can see that the "console.log(images)" only returns the first entry.
Another problem is that it also returns an error:
images[i] is undefined
images[i].style.borderColor = colors[rand];
These are the 2 things I'm struggling with at the moment. It might very well be beginner/easy mistakes since it's my first time working with Javascript.
If there is anything I forgot to mention or you need to know, let me know.
I'm looking forward to a reply.
If I understand you right you should have an HTML page (generated with PHP) that looks like:
<div id="thumbnails">
<img src="..." />
<img src="..." />
<img src="..." />
...
</div>
And if you hover one of the images you want to add a border to this an remove the border if the mouse leaves the image. I assume you are using jQuery, so you could add each image a class e.g. <img class="record" src="..." /> and try the following javascript:
$(function() {
var colors = ["#FC3E6B","#24FF00","#0087F9","#F9F900"];
$('.record').hover(
function() {
var rand = Math.floor(Math.random()*colors.length);
$(this).css('border-style', 'solid');
$(this).css('border-width', '1px');
$(this).css('border-color', colors[rand]);
},
function() {
$(this).css('border-style', 'none');
}
);
}).call(this);
Each time the cursor enters an element (in your case an image) this will get a border, if the cursor leavs it the border will be removed.
Ok, first off: (colors.length - 1) is where you want to go, an array of length 3, has 2 as highest key (0-indexed!)
Second: can you post the actual HTML, or better still: get a jsfiddle up, so we can actually ammend your code, or fork your jsfiddle?
Third: I notice you're using jQuery, have you tried using $('#thumbnails').find('div'); to get your images array? what do you get then?
In case anyone reading this wonders, the reason the original example didn't work is because it is creating a closure. The inner function has access to the variables created in the outer function, but it gets the value of variables when the outer function returns.
In this case, when the code:
images[i].style.borderColor = colors[rand];
executed, the value of i would have been 4, which is outside the range, for each image.
See this for an explanation:
JavaScript closure inside loops – simple practical example

What's the best way to get this data to persist within Javascript event handlers using jQuery

My code is meant to replace radio buttons with dynamic ones, and allow clicking both the label and new dynamic radio element to toggle the state of the hidden with CSS radio box.
I need to send to questions.checkAnswer() three parameters, and these are defined within these initiation loops. However I always get last the last values once the loop has finished iterating. In the past I've created dummy elements and other things that didn't feel right to store 'temporary' valuables to act as an informational hook for Javascript.
Here is what I have so far
init: function() {
// set up handlers
moduleIndex = $('input[name=module]').val();
$('#questions-form ul').each(function() {
questionIndex = $('fieldset').index($(this).parents('fieldset'));
$('li', this).each(function() {
answerIndex = $('li', $(this).parent()).index(this);
prettyRadio = $('<span class="pretty-radio">' + (answerIndex + 1) + '</span>');
radio = $('input[type=radio]', this);
radio.after(prettyRadio);
$(radio).bind('change', function() {
$('.pretty-radio', $(this).parent().parent()).removeClass('selected');
$(this).next('.pretty-radio').addClass('selected');
questions.checkAnswer(moduleIndex, questionIndex, answerIndex);
});
prettyRadio.bind('click', function() {
$('.pretty-radio', $(this).parent().parent()).removeClass('selected');
$(this).addClass('selected').prev('input').attr({checked: true});
});
$('label', this).bind('click', function() {
$(radio).trigger('change');
questions.checkAnswer(moduleIndex, questionIndex, answerIndex);
$(this).prev('input').attr({checked: true});
});
});
});
Is it bad to add a pretend attribute with Javascript, example, <li module="1" question="0" answer="6">
Should I store information in the rel attribute and concatenate it with an hyphen for example, and explode it when I need it?
How have you solved this problem?
I am open to any ideas to make my Javascript code better.
Thank you all for your time.
It's not the end of the world to add a custom attribute. In fact, in many cases, it's the least bad approach. However, if I had to do this, I would prefix the attribute the with "data-" just so that it is compliant with HTML5 specs for custom attributes for forward compatibility. This way, you won't have to worry about upgrading when you want to get HTML5 compliant.
you need to say 'var questionIndex' etc, else your 'variables' are properties of the window and have global scope...
regarding custom attributes, i have certainly done that in the past tho i try to avoid it if i can. some CMS and theming systems occasionally get unhappy if you do this with interactive elements like textareas and input tags and might just strip them out.
finally $(a,b) is the same as $(b).find(a) .. some people prefer the second form because it is more explicit in what you are doing.
If the assignment of the custom attributes is entirely client-side, you must resolve this with jQuery data, something like this:
$("#yourLiID").data({ module:1, question:0, answer:6 });
for the full documentation see here

Categories

Resources