IIFE without parentheses around the function statement - javascript

In the following example, concerning closures, I see this:
function addButtons(numButtons) {
for (var i = 0; i < numButtons; i++) {
var button = document.createElement('input');
button.type = 'button';
button.value = 'Button ' + (i + 1);
button.onclick = function(buttonIndex) {
return function() {
alert('Button ' + (buttonIndex + 1) + ' clicked');
};
}(i);
document.body.appendChild(button);
document.body.appendChild(document.createElement('br'));
}
}
window.onload = function() { addButtons(5); };
How come the onclick method isn't defined as follows?:
button.onclick = (function(buttonIndex) {
return function() {
alert('Button ' + (buttonIndex + 1) + ' clicked');
};
})(i);
Doesn't an IIFE require the following syntax: (function statement)(arguments)?

The original code works because the IIFE appears in an assignment, and so there is no ambiguity that the right part of the assignment is indeed an expression.
Without the assignment, the parser would misunderstand it to be a function declaration, and therefore would throw a syntax error, indicating that the declared function lacks a name. In that case the parentheses are necessary to make it an IIFE.
But it is common practice to always include the parentheses for an IIFE, even when used in an assignment.
In the concrete case you have at hand, you can also assign the click handler as using bind, which returns the function it needs:
button.onclick = function(buttonIndex) {
alert('Button ' + (buttonIndex + 1) + ' clicked');
}.bind(null, i);

You can use element's dataset. For example:
function addButtons(numButtons) {
for (var i = 0; i < numButtons; i++) {
var button = document.createElement('input');
button.type = 'button';
button.value = 'Button ' + (i + 1);
button.dataset.buttonIndex = i + 1;
button.onclick = function() {
alert('Button ' + this.dataset.buttonIndex + ' clicked');
};
document.body.appendChild(button);
document.body.appendChild(document.createElement('br'));
}
}
window.onload = function() { addButtons(5); };

Related

Using setInterval as a counter

I'm trying to create a page that changes every 10 seconds 4 times and then begins again. To do this I made a counter and passed it along with my function. It doesn't even seem like its loading.
I tried using <body onload="start()"> as well.
<script>
var i = 1;
function start(){
i = setInterval(changeEdu, 10000, i);
}
function changeEdu(i){
if(i == 4){
i = 1;
}else{
i++;
}
document.getElementById("edu1").src = "left" + i + ".jpg";
document.getElementById("edu2").src = "right" + i + ".jpg";
return i;
}
</script>
By declaring i as a parameter of your function, your increment will only mutate the local variable and not your global state. Also the return value is ignored.
var i = 1;
function start() {
setInterval(changeEdu, 10000);
}
function changeEdu() {
// ^^
if (i == 4) {
i = 1;
} else {
i++;
}
document.getElementById("edu1").src = "left" + i + ".jpg";
document.getElementById("edu2").src = "right" + i + ".jpg";
}
I believe it is because you are reassigning variable i at the onset of the start() function. setInterval returns a number that is used to cancel the function with the clearInterval(theNumber) function.
I am also quite new to JS, but I would try to delete the reassignment in the start() function and try again.
That is not how setInterval works, it doesn't return your return value.
You need to create a closure.
Also, your startInterval() is never called.
Change it to this and it works:
<script>
(function(){
var i = 1;
function changeEdu(){
if(i == 4){
i = 1;
}else{
i++;
}
// document.getElementById("edu1").src = "left" + i + ".jpg";
// document.getElementById("edu2").src = "right" + i + ".jpg";
console.log(i);
}
setInterval(changeEdu, 10000);
})();
</script>

How to break from Jquery click callback function

I am making my own version of simon Game, but the callback function inside click is not existing after user press wrong input. As a result function handler.patternRepeatPlayer() is getting called recursively for each element of array pattern.I want a solution to break from the else after callinghandler.patternRepeatPlayer just once. The program is working fine in strict mode, but only in non-strict mode inside else , I am not able to break from else.
-- can access the project on Git.
https://github.com/santosh1357/simonGame.git
The flow is like from html -> Function simonnGame.PatternGen from PatternGen -> handler.PatternRepeatPlayer -> PatternRepPlayer -> PatternMatcher -> userInput(here if wrong user input in non-strict mode) -> patternRepeatPlayer
This case is failing as in this case else is not existing after calling the function only once.
//Problematic Function.
userInput: function(){
var userPattern = new Array();var id;
$('img').click(function(){
id = parseInt(this.id,10); userPattern.push(id);handler.effect(id);
if(userPattern.indexOf(id) !== simonGame.PATTERN.indexOf(id)){
if($('.chkStrict:checked').val() === "on"){
var audio = new Audio('sounds/wrong.mp3');
audio.play();
setTimeout(function(){window.location.reload(true)},1000);
} else {
var audio = new Audio('sounds/wrong.mp3');
audio.play();
userPattern.length = 0;
handler.repeatFlag = true;
handler.patternRepeatPlayer(); ****//this is getting called recursivelly rather than quiting after calling once****
return ;
}
}
//End Problematic Functiom
I think there is some misunderstanding on how click callback functions work.
//Fullcode
var simonGame = {
COUNT: 0,
PATTERN: [],
SOUND:[{file:'sounds/sa.mp3'},{file:'sounds/re.mp3'},{file:'sounds/ga.mp3'},{file:'sounds/ma.mp3'},{file:'sounds/pa.mp3'},{file:'sounds/dha.mp3'},{file:'sounds/nee.mp3'}],
patternGen: function(){
var randomId;
randomId = Math.floor(Math.random() * 7);
simonGame.PATTERN.push(randomId);
if(simonGame.COUNT > 20){
alert("You have won the game!!");
window.location.reload(true);
}
simonGame.COUNT += 1;
//debugger;
//console.log("increase count true calling count display " + simonGame.COUNT);
handler.countDisplay();
//console.log("count gen true calling patternPlayer with PATTERN " + simonGame.PATTERN );
handler.patternRepeatPlayer();
}, //close patternGen
patternMatcher: function(genPattern){
//console.log("inside patternMatch");
var genPattern = simonGame.patternGen;
//setTimeout(function(){
//console.log("PATEERN: " + simonGame.PATTERN + "COUNT " + simonGame.COUNT );
//calling user input
console.log("calling user Input");
handler.userInput();
setTimeout(function(){
if(handler.repeatFlag === false){ //execute count gen only if repeat flag is false inside user INPUT
genPattern();
}
},simonGame.COUNT*2000);
//console.log("pattern check true, calling pattern gen");
//},simonGame.COUNT*5000); //c`enter code here`lose setTimeout
}, //close patternMatcher
} //close simonGame
var handler = {
countRepPlayer: 0,
repeatFlag: false,
patternRepeatPlayer: function(){
var repeater = setInterval(function(){
handler.effect(simonGame.PATTERN[handler.countRepPlayer]);
handler.countRepPlayer += 1;
if(handler.countRepPlayer > simonGame.COUNT){
clearInterval(repeater);
//setTimeout(function(){
simonGame.patternMatcher();
//},1000);
handler.countRepPlayer = 0;
}
},1000);//close sestInterval
}, //close patternRepeatPlayer
effect: function(id){
var img = document.getElementById(id);
if(img !== null && id !== undefined){
$( img ).fadeIn(100).fadeOut(100).fadeIn(100);//fadeOut(200).fadeIn(200);
//debugger;
var audio = new Audio(simonGame.SOUND[id].file);
audio.play();
//console.log("id inside effect " + id)
}
},//close effect
countDisplay: function(){
document.getElementById("count").innerHTML = "Count: " + simonGame.COUNT;
}, //close countIncrease
userInput: function(){
var userPattern = new Array();var id;
$('img').click(function(){
id = parseInt(this.id,10);
userPattern.push(id);
handler.effect(id);
console.log(" user " + userPattern);
console.log(" pattern " + simonGame.PATTERN);
if(userPattern.indexOf(id) !== simonGame.PATTERN.indexOf(id)){
console.log(" WRONG USER INPUT ");
if($('.chkStrict:checked').val() === "on"){
var audio = new Audio('sounds/wrong.mp3');
audio.play();
setTimeout(function(){window.location.reload(true)},1000);
} else {
console.log("inside else " );
var audio = new Audio('sounds/wrong.mp3');
audio.play();
userPattern.length = 0;
handler.repeatFlag = true;
handler.patternRepeatPlayer(); ****//this is getting called recursivelly rather than quiting after calling once**.**
return ;
}
}
//reset the userPattern Array
if(userPattern.length === simonGame.PATTERN.length){
userPattern.length = 0;
}
}); //close click.
}
} //close handler
Yes, It will be called recursively, because you set interval for it.
Here you can modify your code:
patternRepeatPlayer: function(){
var repeater = setInterval(function(){
handler.effect(simonGame.PATTERN[handler.countRepPlayer]);
handler.countRepPlayer += 1;
if(handler.countRepPlayer > simonGame.COUNT){
clearInterval(repeater);
//setTimeout(function(){
simonGame.patternMatcher();
//},1000);
handler.countRepPlayer = 0;
}
},1000);//close sestInterval
}
To: (EDITED)
function myCallbackFunction(repeater){
handler.effect(simonGame.PATTERN[handler.countRepPlayer]);
handler.countRepPlayer += 1;
if(handler.countRepPlayer > simonGame.COUNT){
clearInterval(repeater);
simonGame.patternMatcher();
handler.countRepPlayer = 0;
}
}
patternRepeatPlayer: function(){
var repeater = setInterval(function() {myCallbackFunction(repeater);}, 1000);
}
And where you need to call it once, just call myCallbackFunction(repeater)

JS IndexedDB closure/inner function

I would like to understand why in this "loop" I cannot attach the key of element in an event with a closure/inner function. I know what happens with other loops like for,while ... I don't understand the real reason why in this situation this approach doesn't work.
Q: Why can I not pass the key directly?
item.transaction(["itens"], "readonly").objectStore("itens").openCursor()
.onsuccess = function(e) {
var info = e.target.result;
if (info) {
var div = document.createElement("div");
var img = document.createElement("img");
var h2 = document.createElement("h2");
img.src = "../imagens/misc/" + info.value.image;
h2.innerHTML = info.value.title;
div.appendChild(h2);
div.onclick = function() {
//always the lasy key.
console.log(info.key);
};
div.appendChild(img);
box.appendChild(div);
info.continue();
}
};
I know type of solution works...
bt.onclick = (function(index) {
return function (){console.log('iterator: ' + index);}
})(i);
with ({ n: i }) {
bt.onclick = function(index) {
console.log('iterator: ' + n);
};
}
In the example given, you're overwriting div.onclick for each iteration of the cursor.
What's going on might be clearer if you changed it to be:
console.log('overwriting onclick handler to log: ' + info.key);
div.onclick = function() {
console.log(info.key);
};
Or instead, don't overwrite:
console.log('adding onclick listener to log: ' + info.key);
div.addEventListener('click', function() {
console.log(info.key);
});
These will behave differently but perhaps help you to understand what's going on.

Javascript: Using IF condition with imgTmp

The script below works. However, I would like to use it along with a conditional IF statement to say "If the state is Online, then changeIt.style.visibility = 'visible'; else/if not then changeIt.style.visibility = 'hidden';
I really tried, but didn't manage to use IF with imgTmp.
function checkimage() {
var imgTmp = new Image();
imgTmp.onload = function() {
printState("Online");
};
imgTmp.onerror = function() {
printState("Offline");
};
imgTmp.src = "http://xxx/test.png?_=" + (+new Date());
}
function printState(state) {
document.getElementById("div1").innerHTML = " + state + ";
}
printState();
setInterval(checkimage, 200);
Just edit your printState or, create other function to change the something you want visibility...
function printState(state) {
document.getElementById("div1").innerHTML = " + state + ";
if (state === 'Online') {
changeIt.style.visibility = 'visible';
} else {
changeIt.style.visibility = 'hidden';
}
}

Initialize onchange event inside a loop - Javascript

I'm trying to bind an onChange event to 7 dropdowns inside a loop. But when any of dropdown changes, the onChange event for the last one is always executed.
$(function () {
for (var i = 1; i <= 7; i++) {
$('select[id$="bodysys' + i + '"]').change(function () {
if (this.value == "99")
enabletextbox($('input[id$="bodysys' + i + 'spec"]')[0]);
});
}
}
How to make onChange work for all elements separately?
This is called the last one only problem, and is solved using a closure:
$(function () {
for (var i = 1; i <= 7; i++) {
(function (i) {
$('select[id$="bodysys' + i + '"]').change(function () {
if (this.value == "99")
enabletextbox($('input[id$="bodysys' + i + 'spec"]')[0]);
});
})(i);
}
}
It creates a new scope, so when i is changed in the original scope, it will not be changed in the old scope.
$('select[id*="bodysys"]').each(function (index) {
$(this).change(function () {
if ($("option:selected", this).val() == "99")
enabletext($('input[id$="bodysys' + (index + 1) + 'spec"]')[0]);
});
});
This way works too.

Categories

Resources