Okay, I hope you don't all facepalm when you see this - I'm still finding my way around javascript.
I am putting together an RSVP form for a wedding website.
I want the guests to be able to add their names to the RSVP form, but only have as many fields showing as required. To this end, after each name field, there is a link to click, which will, when clicked, show a name field for the next guest.
The code below works... but I am sure it can be tidier.
I have tried to insert a for() loop into the code in several different ways, I can see that the for() loop increments correctly to the last value - but when it does so, it leaves only the last addEventListener in place. I can only assume, that I should be using a different kind of loop - or a different approach entirely.
How should I tidy up the following?
<script>
function showNextGuest(i) {
document.getElementsByTagName(\'fieldset\')[i].style.display = \'block\';
}
function initiateShowNextGuest() {
document.getElementsByTagName('fieldset')[0].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(1);},false);
document.getElementsByTagName('fieldset')[1].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(2);},false);
document.getElementsByTagName('fieldset')[2].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(3);},false);
document.getElementsByTagName('fieldset')[3].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(4);},false);
document.getElementsByTagName('fieldset')[4].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(5);},false);
}
window.onload = initiateShowNextGuest();
</script>
Your intuition is right - a for loop could indeed simplify it and so could a query selector:
var fieldsSet = document.querySelectorAll("fieldset"); // get all the field sets
var fieldss = [].slice.call(asSet); // convert the html selection to a JS array.
fields.map(function(field){
return field.querySelector("a"); // get the first link for the field
}).forEach(function(link, i){
// bind the event with the right index.
link.addEventListener("click", showNextGuest.bind(null, i+1), false);
});
This can be shortened to:
var links = document.querySelectorAll("fieldset a:first-of-type");
[].forEach.call(links, function(link, i){
link.addEventListener("click", showNextGuest.bind(null, i+1), false);
});
function nextGuest () {
for(var i = 0; i < 5; i++){
document.getElementsByTagName('fieldset')[i]
.getElementsByTagName('a')[0]
.addEventListener('click',function(){
showNextGuest(parseInt(i + 1));
}, false);
}
}
Benjamin's answer above is the best given, so I have accepted it.
Nevertheless, for the sake of completeness, I wanted to show the (simpler, if less elegant) solution I used in the end, so that future readers can compare and contrast between the code in the question and the code below:
<script>
var initiateShowNextGuest = [];
function showNextGuest(j) {
document.getElementsByTagName('fieldset')[j].style.display = 'block';
}
function initiateShowNextGuestFunction(i) {
return function() {
var j = i + 1;
document.getElementsByTagName('fieldset')[i].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(j);},false);
};
}
function initiateShowNextGuests() {
for (var i = 0; i < 5; i++) {
initiateShowNextGuest[i] = initiateShowNextGuestFunction(i);
initiateShowNextGuest[i]();
}
}
window.onload = initiateShowNextGuests();
</script>
In summary, the function initiateShowNextGuests() loops through (and then executes) initiateShowNextGuestFunction(i) 5 times, setting up the 5 anonymous functions which are manually written out in the code in the original question, while avoiding the closure-loop problem.
Related
let leadTracker = [];
const inputText = document.getElementById("input-text");
const inputButton = document.getElementById("input-button");
const Listing = document.getElementById("listings");
inputButton.addEventListener("click", function () {
leadTracker.push(inputText.value);
clicker();
});
function clicker() {
for (let i = 0; i < leadTracker.length; i++) {
Listing.innerText += leadTracker[i];
console.log(leadTracker);
}
}
I added bro to the array and then back but bro is repeated. This is exactly the same code I read online but it is still not working fine for me.
Each time you call clicker it's going to add the whole array to Listing, including stuff that's already there.
Did you mean to clear out Listing at the beginning of clicker, before the for loop?
Listing.innerText = ""
You have to ways to do this :
first you have to not use for loop in the function
function clicker() {
Listing.innerText += leadTracker[leadTracker.length-1];
console.log(leadTracker);
}
or you can remove all innerText from the element then re-write it again :
function clicker() {
Listing.innerText = "";
for (let i = 0; i < leadTracker.length; i++) {
Listing.innerText += leadTracker[i];
console.log(leadTracker);
}
}
Each time you click the button you take the content of the text field and add it to the leadTracker array. So the array grows with each click, longer and longer. So far so good.
But in the the clicker function, which also runs on every click, you take the entire content of the array and append it to Listing. They array is not cleared in between clicks, so old items in it will be printed again.
You can either skip using an array at all:
inputButton.addEventListener("click", function () {
Listing.innerText += inputText.value;
});
or replace the inner text of Listing instead of appending it, as suggested in other answers.
I'm trying to make a click handler that calls a function; and that function gets a string and basically slices the last character and adds it to the front, and each time you click again it should add the last letter to the front.
It seem so easy at first that I thought I could just do it using array methods.
function scrollString() {
var defaultString = "Learning to Code Javascript Rocks!";
var clickCount = 0;
if (clickCount === 0) {
var stringArray = defaultString.split("");
var lastChar = stringArray.pop();
stringArray.unshift(lastChar);
var newString = stringArray.join('');
clickCount++;
} else {
var newArray = newString.split("");
var newLastChar = newArray.pop();
newArray.unshift(newLastChar);
var newerString = newArray.join("");
clickCount++;
}
document.getElementById('Result').innerHTML = (clickCount === 1) ? newString : newerString;
}
$('#button').on('click', scrollString);
Right now it only works the first time I click, and developer tools says newArray is undefined; also the clickCount stops incrementing. I do not know if it's an issue of scope, or should I take a whole different approach to the problem?
Every time you click you are actually reseting the string. Check the scope!
var str = "Learning to Code Javascript Rocks!";
var button = document.getElementById("button");
var output = document.getElementById("output");
output.innerHTML = str;
button.addEventListener("click", function(e){
str = str.charAt(str.length - 1) + str.substring(0, str.length - 1);
output.innerHTML = str;
});
button{
display: block;
margin: 25px 0;
}
<button id="button">Click Me!</button>
<label id="output"></label>
It is, in fact, a scoping issue. Your counter in inside the function, so each time the function is called, it gets set to 0. If you want a counter that is outside of the scope, and actually keeps a proper count, you will need to abstract it from the function.
If you want to keep it simple, even just moving clickCount above the function should work.
I do not know if it's an issue of scope
Yes, it is an issue of scope, more than one actually.
How?
As pointed out by #thesublimeobject, the counter is inside the function and hence gets reinitialized every time a click event occurs.
Even if you put the counter outside the function, you will still face another scope issue. In the else part of the function, you are manipulation a variable (newString) you initialized inside the if snippet. Since, the if snippet didn't run this time, it will throw the error undefined. (again a scope issue)
A fine approach would be:
take the counter and the defaultString outside the function. If the defaultString gets a value dynamically rather than what you showed in your code, extract its value on page load or any other event like change, etc. rather than passing it inside the function.
Do not assign a new string the result of your manipulation. Instead, assign it to defaultString. This way you probably won't need an if-else loop and a newLastChar to take care of newer results.
Manipulate the assignment to the element accordingly.
You can use Javascript closure functionality.
var scrollString = (function() {
var defaultString = "Learning to Code Javascript Rocks!";
return function() {
// convert the string into array, so that you can use the splice method
defaultString = defaultString.split('');
// get last element
var lastElm = defaultString.splice(defaultString.length - 1, defaultString.length)[0];
// insert last element at start
defaultString.splice(0, 0, lastElm);
// again join the string to make it string
defaultString = defaultString.join('');
document.getElementById('Result').innerHTML = defaultString;
return defaultString;
}
})();
Using this you don't need to declare any variable globally, or any counter element.
To understand Javascript Closures, please refer this:
http://www.w3schools.com/js/js_function_closures.asp
I have an object like this:
var statistics = {
won: 0,
tie: 0,
lost: 0
};
I have a function that adds 1 to won:
var plus1 = function() {
return statistics.won++;
}
I call that function within an if/else statement like this:
plus1();
But it doesn't work. Does anyone have an idea?
It's probably that x++ returns x instead of x+1.
You are looking for
var plus1 = function() {
return ++statistics.won;
}
Looking at your code I don't really see any reason why you would return your result.
I would rewrite the function to simply be
function plus1() {
statistics.won++;
}
When it comes to having it update, I can't see any were in your code where you actually update the html. After you've run plus1(). If I run console.log(statistics) in my Console I can see that statistic.won goes up whenever I win.
As already mentioned in the comment above, if you run wins() after you've run plus1() it will all work.
This is due to to way pre/post incrementation works in JavaScript:
var one = 1;
var two = 1;
// increment `one` FIRST and THEN assign it to `three`.
var three = ++one;
// assign `two` to `four`, THEN increment it
var four = two++;
So in your code, you're assigning the value of statistics.won to the return value first and then incrementing it. You can see the difference in how they work here.
So, as I mentioned in the comments, return ++statistics.won; is the solution you need.
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()) }));
});
}
}
Hello I'm working on a problem that requires me to change an set array of numbers into an array that returns the original numbers as a function. So we get a return of a2 instead of a[2].
I dont want the answer I just need a hint. I know i can loop through the array and use .pop() to get the last value of the array, but then I dont know how to convert it to a function from there. any hints?
var numToFun = [1, 2, 3];
var numToFunLength = numToFun.length;
for (var i = 0; i < numToFunLength; i++) {
(function(num){
numToFun.unshift(function() {
return num;
});
}(numToFun.pop()))
}
DEMO
basically it pops out a number from the last, builds a function with that number returned, and put back into the first of the array. after one full cycle, all of them are functions.
here's the catch: how this works, it's up to you to research
why the loop does not look like the straightforward pop-unshift:
for (var i = 0; i < numToFunLength; i++) {
numToFun.unshift(function() { //put into first a function
return numToFun.pop() //that returns a number
});
}
and why i did this: (HINT: performance)
var numToFunLength = numToFun.length;
There's three important steps here:
Extract the number value from the array. Within a loop with an iterator of i, it might look like this:
var num = numArray[i];
This is important, because i will not retain its value that it had when you created the new function - it'll end up with the last value it had, once the for loop is finished. The function itself might look like this:
function() { return num; }
There's no reference to i any more, which is important - to understand better, read about closures. The final step would be to add the new function to the array of functions that you want.
...and you're done!
EDIT: See other's answers for good explanations of how to do this right, I will fix mine also though
As others have pointed out, one of the tricky things in javascript that many struggle with (myself included, obviously) is that scoping variables in javascript is dissimilar to many other languages; scopes are almost purely defined by functions, not the {} blocks of, for example, a for loop, as java/C would be.
So, below you can see (and in other answers here) a scoping function can aid with such a problem.
var numArray = [12, 33, 55];
var funcArray = [];
var numArrLength = numArray.length; // Don't do this in for loop to avoid the check multiple times
for(var j=0; j < numArrLength; j++) {
var scopeMe = function() {
var numToReturn = numArray[j];
console.log('now loading... ' + numToReturn);
var newFunc = function() {
return numToReturn;
};
return newFunc;
}();
funcArray.push(scopeMe);
};
console.log('now me');
console.log(funcArray);
console.log(funcArray[0]());
console.log(funcArray[1]());
console.log(funcArray[2]());
console.log(funcArray[1]()); // To ensure it's repeatable
EDIT my old bad answer below
What you'll want to do is something like
var funcArray = [];
for(...) {
var newFunc = function() {
return numArray.pop();
}
funcArray.push(newFunc);
}
The key here is that functions in javascript can be named variables, and passed around as such :)