I'm building small script that lets you change the background image of a page on the fly for design comparisons.
I have a hacky version up and running, but I'm trying to solve this small issue just so I can improve my JS/jQuery skills.
Please look at the jsfiddle here: http://jsfiddle.net/misteroh/Yh22B/6/
The part of the code I'm trying to fix is here:
var clickBind = function() {
for (var j = 0; j < background.length; j++) {
background[j].css({'background': 'url("' + directory + clickBind.data.value + '.jpg")', 'background-size':'cover', 'background-attachment':'fixed'});
crumbSearch[0].css('background', '#F8F8F8');
crumbSearch[1].css({'background': '#F8F8F8'});
if (homepage) {
$('.flexslider').css({'margin-top': '25px'});
}
}
};
for (i = 1; i <= bgOptions; i++) {
bgSwitch.append('<li id="image'+ i + '"></li>');
button.push($('li:last', bgSwitch));
button[button.length - 1]
.css({'background': 'url("' + directory + i + '.jpg")', 'background-size':'cover'})
.click({ value: i }, clickBind);
}
You see that for loop on the bottom? How can I pass the current iteration's value of "i" to the function at the top? I'm assuming the answer is something along the lines of this: http://api.jquery.com/event.data/
My logic behind the code is that in each iteration of the bottom for loop, button[button.length - 1] will be bound with the click event and the clickBind function would use the value of "i" to complete the url of the background image.
Obviously that's not happening. If I simply use "i" in the clickBind's for loop, console.log shows that the value of "i" is always 8.
Please help!
There are a few things you should do to hopefully fix this:
First, your for-loop should look like below.
for (i = 1; i <= bgOptions; i++) {
bgSwitch.append('<li id="image'+ i + '"></li>');
button.push($('li:last', bgSwitch));
button[button.length - 1]
.css({'background': 'url("' + directory + i + '.jpg")', 'background-size':'cover'})
// CHANGED:
.click({value:i},function(event) {
clickBind(event.data.value);
});
}
Next (probably obvious step) is to tell your clickBind function to use this i instead of clickBind.data.value:
var clickBind = function(i) {
for (var j = 0; j < background.length; j++) {
// Replace clickBind.data.value with i:
background[j].css({'background': 'url("' + directory + i + '.jpg")', 'background-size':'cover', 'background-attachment':'fixed'});
crumbSearch[0].css('background', '#F8F8F8');
crumbSearch[1].css({'background': '#F8F8F8'});
if (homepage) {
$('.flexslider').css({'margin-top': '25px'});
}
}
};
Here is a fiddle that works for me
Let me know if this works for you!
Edit: I changed some stuff around in your last for-loop. I am not used to strict mode, so hopefully this method will work for you. If not, we can pursue other options!
Related
EDIT - I changed the code to correctly declare variables below but nothing seems to have changed
I've written code using a for-loop that has to satisfy a number of criteria before executing what's within it. The problem is that, somewhere along the way, the code is getting stuck inside one of the loops, causing the computer to crash.
I've tried breaking the loop but this doesn't seem to help.
function compareKeypoints(varifiedKeypoints) {
outer_loop: for (i = 0; i < varifiedKeypoints.length; i++) {
let initialKeypoint = varifiedKeypoints[i];
for (j = 0; j < varifiedKeypoints.length; j++) {
let comparisonKeypoint = varifiedKeypoints[j];
if (initialKeypoint.part != comparisonKeypoint.part) {
if (Math.abs(comparisonKeypoint.position.x - initialKeypoint.position.x) <= 20
&& Math.abs(comparisonKeypoint.position.y - initialKeypoint.position.y) <= 20) {
if (keypointsCompatible(initialKeypoint.part, comparisonKeypoint.part)) {
console.log("Activating part: " + initialKeypoint.part);
console.log("Activated part: " + comparisonKeypoint.part);
let keypointPair = {
point_1: initialKeypoint.part,
point_2: comparisonKeypoint.part
}
console.log("Pushing parts!");
activeParts.push(keypointPair);
console.log("breaking loop!");
break outer_loop;
console.log("Loop NOT broken!!");
}
}
}
}
}
if (activeParts.length > 0) {
console.log(activeParts);
}
}
function keypointsCompatible(keypoint_1, keypoint_2) {
var outcome = true;
if (activeParts.length > 0) {
compatibility_loop: for (i = 0; i < activeParts.length; i++) {
if (Object.values(activeParts[i]).includes(keypoint_1) && Object.values(activeParts[i]).includes(keypoint_2)) {
console.log(keypoint_1 + " and " + keypoint_2 + " are not compatible because they already exist as " + activeParts[i].point_1 + " and " + activeParts[i].point_2 + " respectively");
outcome = false;
break compatibility_loop;
console.log("Compatibility NOT broken!!");
}
}
}
console.log("Compatibility outcome is " + outcome);
return outcome;
}
The code is suppose to take two values in the same array and compare them. If a number of conditions are met, including if they're a certain distance apart from one another, they will be pushed into a secondary array. If the values already appear in the secondary array, which the keypointCompatible function is suppose to determine, the loop should either continue looking for other candidates or stop before being called again. For some reason, however, the code is getting stuck within the keypointCompatible function when it detects that the values have already appeared in the secondary array and the console will repeatedly print "Compatibility is false" until the browser crashes.
Working Solution
Use let or const instead of var or nothing. Your issue may be related to closures and variables reused between loops. Make sure you use let or const in your loops too. for (let i=0).
When you use let or const, the runtime will create a new instance every time the block or loop iterates. However, using var will reuse the internal allocation.
So what happens with the standard var is the multiple closures or loops each use the same instance of the variable.
Unless you want the var behavior, always use let or const.
Another Solution
Put a newline after the label compatibility_loop
Still Another Solution
The first function is pushing into activeParts. The second function is looping activeParts. This can go on forever, or longer than expected. Pushing into the array could possibly make the loop limit never reached.
Put a log on the length of activeParts in the second function to see if it is growing out of control.
Your code should be OK if varifiedKeypoints.length has reasonable value. And all internal variables are declared properly!
You have two loops (this inner can start at j=i+1 to save time and multiple calculations) with few conditions inside.
function compareKeypoints(varifiedKeypoints) {
outer_loop: for (let i = 0; i < varifiedKeypoints.length; i++) {
let initialKeypoint = varifiedKeypoints[i];
for (let j = i+1; j < varifiedKeypoints.length; j++) {
let comparisonKeypoint = varifiedKeypoints[j];
I'm making a typing game. When multiple players play the game it runs through the same set of functions again. I'm using the variable j as a counter to advance words when they are typed correctly. For some reason, on the second pass on each upkeystroke, it logs j = 1 & j = whatever the value of the previous players last word + 1 is. When each player plays, I want each set of words they are typing to be the same, so that it is fair. I can't figure out why this is happening or even how the variable has 2 values at the same time?!?!?
What gives?
Here's the code in question, but there's a bunch of callbacks that could be involved, although the only place this variable is called is inside this function.
//advances ship on correct typing
function runRace() {
timer();
var j = 1;
//BUG HERE !! Works fine on first iteration but on second
//iterations value jumps beteween 1 and whatever the next
//one is. It's like on every keystroke it reassigns var j
//back to 1, then back to the element number it was on
//last time
//!!! j has 2 values !!!it's keeping the value from the
//prior running of run race
$(document).keyup(function(e){
var targetWord = $(".toType").text();
var typedWord = $("#word").val();
//while (j < gameWords.length){
console.log("j = " + j);
if(typedWord === targetWord){
$(".player").css({left: "+=15px",});
targetWord = $(".toType").text(gameWords[j]);
$("#word").val("");
j++;
}else {
return
};
//}
});
}
If you need to see the rest of the code to figure this out, it's here. Eventhough it's not running right on jsfiddle for reason, it works other then the bug, locally https://jsfiddle.net/ujsr139r/1/
As i mentioned in my comment you're creating multiple listeners everytime runRace() is called.
You could try something like this instead (please note, this isn't the best way to do this, i'm just demoing. Global variables like j in this case aren't a clever idea.:
var j=1; // global because its outside of your function
$(function(){
$(document).keyup(function(e){
var targetWord = $(".toType").text();
var typedWord = $("#word").val();
//while (j < gameWords.length){
console.log("j = " + j);
if(typedWord === targetWord){
$(".player").css({left: "+=15px",});
targetWord = $(".toType").text(gameWords[j]);
$("#word").val("");
j++;
}else {
return
};
//}
});
});
//advances ship on correct typing
function runRace() {
j = 1;
timer();
}
I'm iterating over a table's values to modify them if any changes have been made by the user.
Currently I have something similar to:
for (var i = 0; i < rows.length - 1; i++) {
if (item[5]) {
window.open("modify.php?id=" + id + "&delete=true");
}
}
My question is how I can connect to my modify.php file without breaking the for loop. I tried with
window.location("modify.php?id=" + id + "&delete=true");
too but that's the same story.
I considered using window.open and setting the new windows dimension's to something very small so it appears hidden and closing it again after my PHP has finished executing.
This however seems ridiculously dumb hence why I'm wondering what a/the better approach would be?
It sounds like you want to be using AJAX here. For conciseness, I'll show you what I mean using jQuery.ajax:
for (var i = 0; i < rows.length - 1; i++) {
if (item[5]) {
$.ajax("modify.php?id=" + id + "&delete=true");
}
}
This sends an asynchronous HTTP GET request to the URL provided.
AJAX can be done in vanilla JavaScript too but for reasons of convenience and compatibility I would recommend using a library.
As with any other code, why not simply copy the instructions for later use?
var openItems = [], i;
for (i = 0; i < rows.length - 1; i++) {
if (item[5]) {
openItems.push(id); // Don't open the window just yet, just copy to array
}
}
for (i = 0; i < openItems.length; i++) {
window.open("modify.php?id=" + openItems[i] + "&delete=true");
}
And if you consider that you'd only be opening one window anyway (and you want the first item to be it), then simply do the following:
var openID, i;
for (i = 0; i < rows.length - 1; i++) {
if (item[5]) {
openID = i;
break; // No need to continue iterations
}
}
if (openID) {
window.open("modify.php?id=" + openID + "&delete=true");
}
If you want the last item to be the one you open, simply remove the break; statement.
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
jQuery Looping and Attaching Click Events
(4 answers)
Closed 9 years ago.
I have function process_row that appends tags to html, and those tags are chained to a function upon clicked. (in this case, simply alert(i), its position in the result array).
But however, upon being clicked, the newly generated alerts the length of the entire result array. I have tried many, many changes to try and make it work, but it doesn't.
Strange thou, fab_div.attr("id", result_data[0]); works fine !! In Chrome inspect element the id tags are displayed as they are, but the click function points everything to the last element in the array.
for example, if I do, fab_div.click(function () { alert(result_data[0]) });, I get the name of the LAST element in the array, doesn't matter which element was clicked.
can anyone please explain to me... WHY??
I think it may have something to do with $("<div>") where JQuery thinks it's the same div that it's assigning to. Is there any way around this? The 's are generated dynamically and I would not want to let PHP do the echoing. Plus the content may be updated realtime.
Example dataset :
Smith_Jones#Smith#Jones#janet_Moore#Janet#Moore#Andrew_Wilson#Andrew#Wilson
After many, many changes, still not working:
function process_row(data){
result_array = data.split("#");
if(result_array.length > 0){
result_data =result_array[0].split("#");
for(i = 0; i < result_array.length; i++){
result_data =result_array[i].split("#");
var fab_text = result_data[1] + " " + result_data[2]
var fab_div = $("<div>");
fab_div.addClass('scroll_tap');
fab_div.attr("id", result_data[0]);
fab_div.append(fab_text)
// fab_div.click(function () { alert(i) });
// ^ not working, try appending list of id's to id_list
id_list.push(result_data[0])
$('#ls_admin').append(fab_div)
}
for(j = 0; j < id_list.length; j++){
$('#' + id_list[j]).click(function () { alert(j) })
}
}
}
Original Attempt:
function process_row(data){
result_array = data.split("#");
if(result_array.length > 0){
result_data =result_array[0].split("#");
for(i = 0; i < result_array.length; i++){
result_data =result_array[i].split("#");
var fab_text = result_data[1] + " " + result_data[2]
var fab_div = $("<div>").append(fab_text).click(function () { alert(i) });
fab_div.addClass('scroll_tap');
fab_div.attr("id", result_data[0]);
$('#ls_admin').append(fab_div)
}
}
}
If you must use an alert, then you can encapsulate the click handler in a self executing function and pass the index to it. Like,
(function (index) {
fab_div.click(function () {
alert(index);
});
})(i);
Although, this is not a clean way to do it. Otherwise, if you are looking to just manipulate the div element is any way, then adding any method directly will also work. Like,
fab_div.click(function () {
alert($(this).attr('id'));
});
You can refer a jsFiddle here
Wonky Solution, but it worked! Haha! Big thanks to Kevin B.
function process_row(data){
result_array = data.split("#");
if(result_array.length > 0){
result_data =result_array[0].split("#");
for(i = 0; i < result_array.length; i++){
result_data =result_array[i].split("#");
var fab_text = result_data[1] + " " + result_data[2]
var fab_div = $("<div>").append(fab_text);
fab_div.addClass('scroll_tap');
fab_div.attr("id", result_data[0]);
$('#ls_admin').append(fab_div)
}
$("#ls_admin").children(this).each(function( index ) {
$(this).append($(this).click(function () { alert($(this).text()) }));
});
}
}
inputTabTitle: function(){
origin = template.clone();
$("#inputTabCount").change(function(){
tabcount = parseInt($("#inputTabCount").val());
if(tabcount > 0){
tab = origin.find("label").text();
for(i = 1; i <= tabcount; i ++){
origin.find("label").text(tab + i);
origin.find("label").attr("for", "inputTabTitle" + i);
origin.find("input").attr("id", "inputTabTitle" + i);
$("#tabCount").append(origin);
}
}
})
}
set n = 3
When append to "#tabCount", only one element insert, actually should be three.But this code append performed like replace.Why?
And when I add "origin = origin.clone()" before loop end, it worked well, three element inserted.
You clone your template only once. That means: Two times you append the 'origin' to a place, where it already is.
To get, what you want (or I think you want), the cloning MUST be in the loop.
Please notice further that you pollute the GLOBAL space when you define variables such as 'tabcount' without the 'var'. I fixed that in your source code, too.
Rewrite the function like that below.
But be warned: The amount of tabs is being inserted every time the value changes. That means:
Value changes to 1 --> one tab is made
Value changes to 2 --> two ADDITIONAL tabs are made.
.
inputTabTitle: function(){
$("#inputTabCount").change(function(){
var tabcount = parseInt($("#inputTabCount").val());
if(tabcount > 0){
tab = template.find("label").text();
for(i = 1; i <= tabcount; i ++){
var origin = template.clone();
origin.find("label").text(tab + i);
origin.find("label").attr("for", "inputTabTitle" + i);
origin.find("input").attr("id", "inputTabTitle" + i);
$("#tabCount").append(origin);
}
}
})
}