How to Update a Variable that Relies on Code After It? (JavaScript) - javascript

My Problem:
Hey everyone. I'm trying to write some code that changes the brightness of an element based on how far away it is from the current item. Everything runs according to plan, however, as it moves down the list all of the items before the current item begin to return "NaN" as their value. This makes sense, as the "currentItemIndex" variable is only given a value once it finds the current item's position, so there's no value for the "currentItemIndex" until it reaches the current item within the list.
What I've Tried:
I've tried declaring the "currentItemIndex" variable before it searches for the current item, giving it either a value of 0 to start or an empty string. The starting value of 0 results in the "currentItemIndex" staying at 0 no matter what, and the empty string just produces the same result as when there was no variable declared there in the first place.
I'm not sure how to get the "currentItemIndex" before searching for the current item and not have it affect the variable when it checks for the current item. Is anyone able to help me out?
My Code:
JavaScript:
var items = document.getElementsByClassName('item'); // Gets all of the items
for (i = 0; i < items.length; i++) { // For each of the items, this:
var itemClass = items[i].classList // Get class of the item
if (itemClass.contains('current-item')) { // If it is the current item, this:
var currentItemIndex = i; // Set the current item's position to the target's position
}
var brightness = (100 + (Math.abs(currentItemIndex - i) * 50)); // Calculate how much the brightness should change based on the target's distance from the current item
items[i].style.filter = 'brightness(' + brightness + '%)'; // Apply that brightness to the target
}

You'll need to find the currentItemIndex first, then do the loop setting the brightness:
const items = document.getElementsByClassName("item"); // Gets all of the items
let currentItemIndex;
for (let i = 0; i < items.length; i++) { // For each of the items, this:
const itemClass = items[i].classList // Get class of the item
if (itemClass.contains("current-item")) { // If it is the current item, this:
currentItemIndex = i; // Set the current item"s position to the target"s position
break;
}
}
for (let i = 0; i < items.length; i++) { // For each of the items, this:
const brightness = (100 + (Math.abs(currentItemIndex - i) * 50)); // Calculate how much the brightness should change based on the target"s distance from the current item
items[i].style.filter = "brightness(" + brightness + "%)"; // Apply that brightness to the target
}
(With more context, it may be that there's a more concise way to find the index of that item [the first for loop], but the above works and it's simple.)
Side note: I added a declaration for i, so the code isn't relying on what I call The Horror of Implicit Globals. I strongly recommend using strict mode, so that's the error it always should have been.

You need to break the problem down into two steps:
find the index of the current item
calculate and set the brightness for every item
// get all of the items as an array
let items = Array.from(document.getElementsByClassName('item'));
// 1. find the index of the current item
let currentItemIndex = items.findIndex(item => items.classList.contains('current-item'));
// 2. calculate and set the brightness for every item
items.forEach((item, i) => {
let brightness = (100 + (Math.abs(currentItemIndex - i) * 50));
item.style.filter = `brightness(${brightness}%)`;
});

Related

How to loop through functions by name in P5js / JavaScript

I'm working on a basic P5 program that requires up to ten input boxes.
So I need to first create the button instances, using e.g.
factor1Input = createInput(""); // create DOM element for input field
factor1Input.position(leftMargin, topMargin + 50); // place button on screen
factor1Input.changed(this.factor1update); // call function when value changes
factor1Button = createButton('Update Factor'); // create DOM element for button
factor1Button.position(100, 100); // position button
Then toggle their visibility using e.g.
factor1Input.show(); // toggle display on
factor1Button.show();
factor1Input.hide(); // or toggle it off
factor1Button.hide();
But because I'll have up to 10, this will require a ton of repetitive code.
So I want to create a loop that goes something like (e.g. just for the show function);
for (let i = 1; i < factorCount; i++){
let fci = "factor" + i + "Input";
let fcb = "factor" + i + "Button";
fci.show();
fcb.show();
}
But I'm getting the following error:
Uncaught TypeError: fci.show is not a function
Which suggests some kind of type mismatch, i.e. I can't seem to just compile a string, and have this recognized as the JavaScript function.
Any suggestions?
fci will be a string so the String class will not have a method show, You will get an exception. Instead
You can write in this way
var factorObject = {
factor1Input:createInput("")
}
factorObject['factor1Input'].position(leftMargin, topMargin + 50);
For show
factorObject['factor1Input'].show();
Here in the loop
for (let i = 1; i < factorCount; i++){
let fci = "factor" + i + "Input";
factorObject[fci].show();
}
The reason why your code doesn't work is because you create a string and try to call it like a variable with the same name
let fci = "factor" + i + "Input";
fci.show(); // fci is just a string 'factor1Input', has nothing in common with factor1Input variable
You should use arrays Arrays and instead of keeping input number (e.g. index) inside a name, let it be the index of an item in the array
const factorCount = 10
const inputs = []
const buttons = []
// example of creating inputs and buttons in a loop, you can create them manually if you want,
// but don't forget to .push them to respective array
for (let i = 1; i < factorCount; i++){
const input = createInput("");
input.position(leftMargin, topMargin + 50 * i); // using index to calculate top margin
input.changed((value) => this.factorUpdate(i, value)); // notice the change here
inputs.push(input)
const button = createButton('Update Factor');
button.position(100, 100 + 50 * i); // also using index to calculate top margin
buttons.push(button)
}
function showInput(index) {
inputs[index].show()
buttons[index].show()
}
function hideInput(index) {
inputs[index].hide()
buttons[index].hide()
}
showInput(3) // shows 3rd input and button
hideInput(4) // hides 4th input and button
Notice also how I changed your this.factor1update method call. The same way you don't want to have 10 separate variables for 10 elements, you don't want to have 10 methods to handle changes on those 10 elements (what if there was 10000 elements?). Instead, create one method factorUpdate that will receive item index and the value that was changed and use that to handle the input change
added:
for (let i = 0; i < factorCount; i++){ // changed 1 to 0 here, it was a typo
const input = createInput("");
input.position(leftMargin, topMargin + 50 * i);
input.changed(() => factorUpdate(i)); // we call factorUpdate with index of an element
inputs.push(input)
const button = createButton('Update Factor');
button.position(185, topMargin + 50 * i);
buttons.push(button)
}
function factorUpdate(i, event){
// argument i is now an index of unfocused input
console.log("input index: " + i + ", value: " + inputs[i].value());
}
Note also how input.changed() works: you edit the input, then you click somewhere else on the page to unfocus it, and that's when this event is triggered. With that in mind, buttons here don't actually do anything as there are no click listeners assigned to them

if statement executes even with false condition

I've written some code which is a basic up/down voting list.
https://alasdairjames.github.io/up-down-counter1/
This works absolutely fine, apart from with the last list item. All the other list items up and down vote as they should.
With the last list item, if I 'down' vote it a few times, and then 'up' vote it, even if its counter is lower than its parent prev sibling counter, the if statement somehow still runs.
I've checked through all the code and I can't see where the problem is.
//Move it up
$(".testMoveUp").on("click", function(event){
// select the counter, increase it
const counter = $(event.target).siblings(".datasetCounter");
let counterNew = +$(counter).text()+1;
$(counter).text(counterNew);
//select this and previous counters
var thisCounter = $(event.target).siblings(".datasetCounter").text();
var prevCounter = $(event.target).parent().prev().children(".datasetCounter").text();
console.log(thisCounter);
console.log(prevCounter);
//move if appropriate
if ( thisCounter > prevCounter) {
var parent = $(event.target).parent();
var prevParent = $(event.target).parent().prev();
$(parent).insertBefore(prevParent);
}
});
//Move it down
$(".testMoveDown").on("click", function(event){
// select the counter, increase it
const counter = $(event.target).siblings(".datasetCounter");
let counterNew = $(counter).text()-1;
$(counter).text(counterNew);
//select this and previous counters
var thisCounter = $(event.target).siblings(".datasetCounter").text();
var nextCounter = $(event.target).parent().next().children(".datasetCounter").text();
console.log(thisCounter);
console.log(nextCounter);
//move if appropriate
if ( thisCounter < nextCounter) {
var parent = $(event.target).parent();
var nextParent = $(event.target).parent().next();
$(parent).insertAfter(nextParent);
}
});
if (thisCounter < nextCounter) -- you compare strings here, not numbers.
Use parseInt() to store numeric values in thisCounter and nextCounter:
var thisCounter = parseInt($(event.target).siblings(".datasetCounter").text(), 10);
The problem is on this two lines:
var thisCounter = $(event.target).siblings(".datasetCounter").text();
var nextCounter = $(event.target).parent().next().children(".datasetCounter").text();
You are getting the text value and comparing it.
The easier way to fix it is just to parse the texts to numbers. Since you are sure they will always be numbers, you can simply add '+' on your comparisons:
if (+thisCounter < +nextCounter) {
...
}
Note: remember to add it to both comparisons, not only one
String "9" is greater than string "10". Make sure to convert your values to numbers before comparing them.

How can you dynamically slice an array in Javascript/jQuery?

I have a photo gallery that includes images that will be continuously uploaded. The PHP array has been converted/encoded to a JSON array so that I can manipulate the data with JavaScript.
Ideally, I would like to click a button ("Next Set" in the CodePen example) and load the next set (of 2) thumbnail images. This is in an effort to not load all of the images at once, which could be hundreds.
Problem: I cannot figure out how to dynamically slice the array on click (next 5 images). I can of course load, say, 2 at a time:
myArray.slice(0,2);
myArray.slice(3,5);
However, this will not work because images will be continuously added to the gallery. Furthermore, I would have to have too many sets of the above to keep slicing 5 out at a time.
I have tried:
Splitting the array into smaller arrays
for loops and $.each loops
I essentially need to be able to move the start and end index of the slice by (for example) 2 on click. Right now it just keeps slicing the same two images because the slicing is not dynamic.
Here is my CodePen
I don't think there's a way to do exactly what you want, but you can just keep track of where you were in the array and do a slice from there, like this:
var nextSet = myArray.slice(lastIndex, lastIndex + 2);
Replace your existing click() with this (including the declaration of lastIndex) to try it:
var lastIndex = 0
$('.button').click(function() {
var nextSet = myArray.slice(lastIndex, lastIndex + 2);
lastIndex += 2;
for (var i = 0; i < 2; i++) {
var li = $('<li/>').attr('role', 'menuitem').appendTo('.myList').append('<img src=' + nextSet[i] + '>');
}
});
Note that I've moved the slice() line outside the for loop. There's no need to slice a new array for every iteration.
Here's a CodePen using .slice().
An alternate method is to use to shift() to peel off the first item in the array with each iteration:
var nextItem = myArray.shift()
This is destructive though (it removes the item from the original array), so you'll need to make a copy of the original array first if you want to use it for anything else. Replace your click() with:
$('.button').click(function() {
for (var i = 0; i < 2; i++) {
var nextItem = myArray.shift();
var li = $('<li/>').attr('role', 'menuitem').appendTo('.myList').append('<img src=' + nextItem + '>');
}
});
Here's a CodePen using .shift().
your problem is simple i think. you do a slice and allways get back the same array
var array = [0,1,2,3,4,5];
let newArray1 = array.slice(0,2); // returns a new array
let newArray2 = array.slice(0,2); // returns the same new array
for(var i = 0; i < 2; i = i+2) {
result = array.slice(i, i+2);
console.log(result);
}

How to compare Array value to result of 'for' loop in javascript

I have an empty array (called zoomthumbsarray) which gets values pushed to it whilst a 'for' loop is running. This 'for' loop is checking if a thumbnail image is present in the backend against the particular product the user is viewing. If there is an image it gets added into a vertical slider. The current issue is there are non colour specific images (like lifestyle shots) that are being added into the slider multiple times.
So I need to check if the image found in the for loop is currently stored in the array. If it is present, the image has already been generated and I don't want it to get pulled into the slider again. If it hasn't then the image will get added.
Below is the code I am working on. I would presume indexOf would be used but can't get this to work.
Any help would be really appreciated.
var zoomthumbsarray = [] // Empty array which gets populated by .push below during loop
for (var i = 0; i < storeImgsArr.length; i++) { // storeImgsArr finds the quantity of attributes present against the product. This loops and increments counter if there is another attibute image
for (var e = 0; e < storeImgsArr[i].images.imgL.length; e++) { // Loop and increment counter if there is a Large image
zoomthumbsarray.push(storeImgsArr[i].images.imgS[e].slice(-16)); // Slices off last 16 characters of image path i.e. _navy_xsmall.jpg or 46983_xsalt1.jpg and pushes this into 'zoomthumbsarray' array
// if statement sits here to build the html to add the image to the slider
}
}
zoomthumbsarray = [] // Resets array to zero
ANSWER
As answered by Chris I used $.unique to only keep unique values in the array.
Then wrap an if statement around the code to build the thumb image html if the array === 0 or if the current image isn't already in the array.
Updated code below:
var zoomthumbsarray = [] // Empty array which gets populated by .push below during loop
for (var i = 0; i < storeImgsArr.length; i++) { // storeImgsArr finds the quantity of attributes present against the product. This loops and increments counter if there is another attibute image
if (zoomthumbsarray === 0 || zoomthumbsarray.indexOf(storeImgsArr[i].images.imgS[e].slice(-16)) < 0) { // If statement is true if array === 0 or if the current image isn't already in the array
for (var e = 0; e < storeImgsArr[i].images.imgL.length; e++) { // Loop and increment counter if there is a Large image
zoomthumbsarray.push(storeImgsArr[i].images.imgS[e].slice(-16)); // Slices off last 16 characters of image path i.e. _navy_xsmall.jpg or 46983_xsalt1.jpg and pushes this into 'zoomthumbsarray' array
zoomthumbsarray = $.unique(zoomthumbsarray); //Keeps only unique elements
// if statement sits here to build the html to add the image to the slider
}
}
}
zoomthumbsarray = [] // Resets array to zero
Some cheap and dirty ideas:
Using underscore/lodash:
zoomthumbsarray = _.uniq(zoomthumbsarray); //Keeps only unique elements
jQuery has one as well:
zoomthumbsarray = $.unique(zoomthumbsarray); //Keeps only unique elements
then you loop through the array and build HTML.
Update:
There's something a bit odd about the rest of the JS. Might this work (if you're using a new enough browser)?
var zoomthumbsarray = [];
storeImgsArr
.map(function(item) { return item.images.imgS; })
.forEach(function(imgS) {
zoomthumbsarray = zoomthumbsarray.concat(imgS.map(function(imagePath) {
return imagePath.slice(-16);
}));
});
zoomthumbsarray = $.unique(zoomthumbsarray);
I have tried indexOf (see first if statement below) but this doesn't work.
As #elclanrs said, indexOf does return the index in the array not a boolean. You only will need to see if it's >= 0 to test whether an image is already contained in the array.
var zoomthumbsarray = [];
for (var i = 0; i < storeImgsArr.length; i++) {
for (var e = 0; e < storeImgsArr[i].images.imgL.length; e++) {
var image = storeImgsArr[i].images.imgS[e].slice(-16);
if (zoomthumbsarray.indexOf(image) < 0) { // not yet in the array
zoomthumbsarray.push();
// and build the html to add the image to the slider
}
}
}
If you have really lots of images and notice this starts slowing the page down, then there are too many images in your page anyway. No, joke aside; …then check the optimisation by #Ivey.
instead of using an array you can use an object to store the images as keys and a dummy value (possibly true). then you can extract the keys from this object.
var images = {};
for (var i = 0; i < storeImgsArr.length; i++) {
for (var e = 0; e < storeImgsArr[i].images.imgL.length; e++) {
images[storeImgsArr[i].images.imgS[e].slice(-16))] = true;
}
}
var zoomthumbsarray = [];
for(var k in images) {
zoomthumbsarray.push(k);
// build the html to add the image to the slider
}
EDIT: Added build html comment

Using shift() and push() to loop array values vs. using a counter variable, what is the best approach?

I'm looping through a set of images within an array of animation frames. There are 7 images, and looping from 1-7 completes the animation. I need this animation to loop indefinitely, but I was wondering which of these is the best approach:
Loop by modifying array
/* Pull image from start of array. */
var image = frames.shift();
/* Process image. */
...
/* Add image back to end of array. */
frames.push(image );
Loop using counter variable
/* Pull image by counter offset. */
var image = frames[counter];
/* Process image. */
...
/* Increment or reset counter value. */
counter + 1 === frames.length ? counter = 0 : counter = counter + 1;
Is there a reason I'd chose one over the other? Alternatively, is there a better approach to this?
Modifying the array is going to be more expensive than simply using a variable to keep track of your position in the array. The better way to do this, if you're looping indefinitely, seems to just be to use a while loop (rather than using a for loop where you reset the counter inside):
var i = 0;
while (true) {
doSomething to array[i];
i = (i+1) % array.length;
}
However if your goal really is having an animation proceed indefinitely every time a given interval elapses, a loop isn't ideal at all. Use setInterval instead.
var frames = ...; //your images
var i = 0;
function animate() {
do something to frames[i];
i = (i+1) % array.length;
}
setInterval(animate, time_between_runs);
where time_between_runs is how much time should elapse before the function is called again.
Alternatively a circular linked list also can be used I think. To turn an array of objects into a circular linked list:
frames.forEach(function(elem, index) {
elem.next = frames[index + 1] || frames[0];
});
And now you can do something like this:
setInterval(function() {
frame = frame.next;
....
}, delay);
One possibility is to ditch the array and use a linked list.
Make each frame an object that points to the next object. The last one then points to the first. Then all you need to do is reference the next object.
var curr = first; // reference to first frame object
setInterval(function() {
// process image
curr.image.doSomething();
// proceed to next
curr = curr.next;
}, 1000);
No counters to mess with this way.
Setting up the linked list is usually pretty simple, and can likely be done with just a little modification to the current code that's setting up the Array.
var first = new Frame(); // This is your entry point
var current = first; // This holds the current frame during setup
for (var i = 0; i < totalFrames; i++) {
current.next = new Frame(); // Make the current reference a new frame
current = current.next; // Make the new frame current
}
current.next = first; // circular reference back to the first.
function Frame() {
// set up this frame
}
Then first is your starting point.
Or the linking could be done within the Frame constructor.
var first = new Frame(null);
var current = first;
for (var i = 0; i < totalFrames; i++) {
current = new Frame(current);
}
current.next = first;
function Frame(currFrame) {
// link this frame to the given one
if (currFrame)
currFrame.next = this;
// set up the rest of this frame
}

Categories

Resources