I don't know how to fix problem with events? - javascript

I am building a battleship game in js. I added event listener on targeted element of the grid and I am trying to make a function that takes one argument (boat length) and according to that argument, function should render the boat on my grid. Problem is that no matter what argument I passed in to the function it renders the boat same length. I think that problem is event listener that stays the same when I clicked the element first time.
Here is the code:
function placeBoat (boatLength){
domElements.yourGrid.onclick = function(event){
let target = event.target;
console.log(event)
let clickedPosition = target.id
let clickedPositionId = clickedPosition.slice(4, 6)
let clikedPositionInt = parseInt(clickedPositionId);
for(let i = 0; i < boatLength; i++){
target.style.background = "green";
target = document.querySelector("#cell" + clikedPositionInt)
clikedPositionInt += 10;
}
}
}
domElements.boat1Button.addEventListener("click", placeBoat(3))
domElements.boat2Button.addEventListener("click", placeBoat(4))
domElements.boat3Button.addEventListener("click", placeBoat(5))

addEventListener's second argument is the listener function ( https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener ). However, you're calling the function placeBoat instead of passing a function as an argument.
Let's start with a simple explanation. Define some new functions without arguments and add event listeners using them:
function placeBoat3() {
placeBoat(3);
}
function placeBoat4() {
placeBoat(4);
}
function placeBoat5() {
placeBoat(5);
}
domElements.boat1Button.addEventListener("click", placeBoat3)
domElements.boat2Button.addEventListener("click", placeBoat4)
domElements.boat3Button.addEventListener("click", placeBoat5)
As you can see, now second arguments to addEventListener are function names without parenthesis. Once you are comfortable with that idea, you can move forward and use arrow functions. Get rid of the new functions and call addEventListeners like below:
domElements.boat1Button.addEventListener("click", () => placeBoat(3))
domElements.boat2Button.addEventListener("click", () => placeBoat(4))
domElements.boat3Button.addEventListener("click", () => placeBoat(5))
() => placeBoat(3) is a shorthand to define a function with no arguments and which calls placeBoat(3) in its body. You can learn more about them at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

Related

Javascript- Immediately-invoked function expressions in an event listener

I'm still a beginner in learning javascript. Recently, I learned about IIFE, and there's an example that I could not figure out why there's a difference in results between the following two,
**First one (the callback function is IIFE)-
**
button.addEventListener('click', (function() {
let count = 0;
return function() {
count += 1;
if (count === 2) {
alert('This alert appears every other press!');
count = 0;
}
};
})());
**Second one-
**
button.addEventListener('click', function() {
let count = 0;
return function() {
count += 1;
if (count === 2) {
alert('This alert appears every other press!');
count = 0;
}
};
});
So, apparently, for the first one, when I click on the button, the alert will come up every other press.
As for the second one, the alert will never show up, because the variable "count" will never reach 2.
I just cannot figure out how and why does this kind of behavior happen, and I don't understand the difference between IIFE being a callback function in EventListener and a regular function being a callback in EventListener.
Thanks!
The difference is that in the first code block, the outer function is being called immediately, and returning the inner function, which is then used as the event listener. In your second example, the outer function is used as the event listener, so it isn't called until/unless the event is triggered, and it doesn't do anything but create a function that it returns (which is then ignored).
It may help to pull things apart a bit. Here's a slightly-modified version of the first example, but without using an IIFE:
function createListener() {
let count = 0;
return function () {
count += 1;
if (count === 2) {
alert("This alert appears every other press!");
count = 0;
}
};
}
const listener = createListener();
button.addEventListener("click", listener);
The only difference is that in the version in the question, createListener is temporary and unnammed, and there's no listener variable.
Here are some other questions whose answers may be useful:
What is the difference between a function call and function reference? (for the distinction between calling the outer function and using its return value as the listener vs. using the outer function as the listener directly)
How do JavaScript closures work? (for the aspect of the listener where count is entirely private)

How can i pass parameters to a function inside click eventlistner

function changeColor(btn){
btn.style.backgroundColor = "red";
}
let btn1 = document.getElementById("1");
btn1.addEventListener("click",changeColor(btn1));
I know that calling a function in the "addEventListner" immediately call the function . But I do need to pass a object to my function inside "addEventListner()" because I'm planning to use only one general function to handle clicks of 10 buttons.
From the above comment ...
"A handler function gets passed an Event type to it. An UI event always features a currentTarget and a target property. The OP should access the former ... event.currentTarget.style.backgroundColor = "red";."
Instead of using the color changing function as callback one could implement a button specific click handler which forwards its current target to a generic function which changes the background color of any passed element reference.
function changeElementColor(elm){
elm.style.backgroundColor = 'red';
}
function handleButtonClick(evt) {
changeElementColor(evt.currentTarget);
}
document
.querySelectorAll('button')
.forEach(btnElm =>
btnElm.addEventListener('click', handleButtonClick)
);
<button><em>Hallo</em></button>
<button><b>World</b></button>
You have to create an anonymous function () => changeColor(btn)
function changeColor(btn){
btn.style.backgroundColor = "red";
}
let btn1 = document.getElementById("1");
btn1.addEventListener("click", () => changeColor(btn1));

naming a function and adding event listeners to it causes "not defined"

I'm still new to JavaScript and i'm having the below issue.
I'd like to name/declare my function and then use its name as listener to add/remove click events easily. the main reason for this is so I can remove the click events whenever some condition happens.
Brief of what i'm trying to achieve:
function game() {
//
}
The problem i'm having is when I add the event like this:
for (let i = 0; i < cards.length; i++) {
card = cards[i];
card.addEventListener('click', game);
}
I get an error with the named function game that says:
i is not defined
However, this error doesn't happen when I put the function as anonymous inside the listener parameter.
for (let i = 0; i < cards.length; i++) {
card = cards[i];
card.addEventListener('click', function game() {
//
}
Declaring i globally didn't work nor passing i as parameter.
Full code with anonymous function: Here
Full code with named function (what I want to work): Here
To access different is, you need different closures, and therefore you got different function references. You could store them directly in the card to be able to remove them later on:
for (let i = 0; i < cards.length; i++) {
const card = cards[i];
card.addEventListener('click', card._click = function game() {
//
});
}
Then you can remove it later with:
card.removeEventListener("click", card._click);
The same also works if you move game outside and add a curried parameter for i (might be cleaner):
const game = i => () => {
// ...
};
for (let i = 0; i < cards.length; i++) {
const card = cards[i];
card.addEventListener('click', card._click = game(i));
}
Your issue is that your game function doesn't know about i because i doesn't exist within your game function due to variable scoping. In order to fix this you can pass i as a argument into game like so:
card.addEventListener('click', _ => game(i));
In the above line, your calling an arrow function, which then calls the game function. This allows you to pass an argument into game.
And then in your game method accept the variable i as an argument:
function game(i) {
See working example here
You should read about lexical scoping in javascript to understand why i is not available inside the named function game
Inside game function, it can only refer variables that are within in lexical scope (or inside this which is a topic of its own)
Simply put, you can pass the 'i' to the game to get it working. But since the addEventListener expects a function, you should create a function factory that employs a bit of closure
edit your game as
let game = (i) => () => {
//existing game function
}
now use card.addEventListener('click', game(i)); and it should work as expected
Here is a working fiddle for your reference : https://jsfiddle.net/uch1arny/2/

JQuery on click event listeners and functions, fire differently with different parameters?

I have elements that when I click on them I need to run a function and that function needs to know what element was clicked on.
Example A:
var elements = $(".config-cell");
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener("click", function() {
console.log("clicked");
});
}
When calling the function right there it works fine, however I don't know how to pass through the element to the function so it can use it.
So I tried using this method and found something strange.
Example B:
var elements = $(".config-cell");
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener("click", this.cellCrop());
}
When simply calling the function located elsewhere I noticed when loading the window it just automatically fires the function and then it doesn't even add the event listener so any time you click after that nothing happens.
Bottom line I would like to be able to pass through the current element being clicked on and have it fire a function. But I would like to know out of curiosity why method B works the way it does.
Learned that it knows which to use because 'forEach' has a callback with parameters
.forEach(function callback(currentValue[, index[, array]])
For instance: How does this call back know what is supposed to be
'eachName' and 'index'
var friends = ["Mike", "Stacy", "Andy", "Rick"];
friends.forEach(function (eachName, index){
console.log(index + 1 + ". " + eachName); // 1. Mike, 2. Stacy, 3. Andy, 4. Rick
});
And can you do this with .addEventListener instead of setting it as a
var?
That being said is there a way to have it pass variables with your own function? Like:
var passthrough = 5;
$(".config-cell").on("click", function(passthrough) {
var five = passthrough;
console.log(five);
});
First, this.cellCrop() calls the function, this.cellCrop passes it. So if you wanted to set the listener it would have been
elements[i].addEventListener("click", this.cellCrop);
Now to actually get the element clicked on inside the function you can do it a couple ways.
Using currentTarget / target from the event object that is automatically passed to event listeners
elements[i].addEventListener("click", function(event){
//the actual element clicked on
var target = event.target;
//the element the event was set on, in this case whatever elements[i] was
var currentTarget = event.currentTarget;
});
//same using jQuery
$(elements[i]).click(function(event){
var target = event.target;
var currentTarget = event.currentTarget;
});
Using the this keyword
elements[i].addEventListener("click", function(event){
//this will refer to whatever elements[i] was
var target = this;
});
//same using jQuery
$(elements[i]).click(function(event){
var target = $(this);
});
This would apply the same with using object method:
obj = {
cellCrop:function(event){
var target = event.target;
/* etc */
},
someOtherMethod:function(){
//...
elements[i].addEventListener("click",this.cellCrop);
//or jQuery
$(elements[i]).click(this.cellCrop);
//...
}
};
obj.someOtherMethod();
How does this call back know what is supposed to be 'eachName' and 'index'
Because documentation for the forEach method tells the person using it how it is going to be called. So you write the callback based on that documentation.
For instance the callback for forEach usually takes the form of
function callback(currentValue[, index[, array]])
Which means inside forEach() it is going to call your callback in this fashion
function forEach(callback){
//`this` inside forEach is the array
for(let i=0; i<this.length; i++){
callback(this[i], i, this);
}
}
As for passing arbitrary data to the function, there are a few ways it can be done:
Wrap a call to a function in an anonymous function and explicitly call the function with more arguments
obj = {
cellProp:function(event,a,b,c){
var element = event.currentTarget;
}
}
//normal anonymous function
elements[i].addEventListener('click',function(e){
obj.cellProp(e,1,2,3);
});
//fat arrow function
elements[i].addEventListener('click',e=>obj.cellProp(e,1,2,3))
In the above a, b and c will respectively contain the values 1,2 and 3
You can also use methods like bind which will change the thisArg(see this question to see more on that) of the function but also pass in arguments to the function
obj = {
//note event comes last as bind, call, and apply PREPEND arguments to the call
cellProp:function(a,b,c,event){
//`this` will change depending on the first
//argument passed to bind
var whatIsThis = this;
var element = event.target;
}
}
//passing obj as the first argument makes `this` refer to
//obj within the function, all other arguments are PREPENDED
//so a,b, and c again will contain 1,2 and 3 respectively.
elements[i].addEventListener('click', obj.cellProp.bind(obj,1,2,3) );
In the case of jQuery, you can also pass data in using an object at the time of setting up the event:
obj = {
cellProp:function(event){
var data = event.data;
console.log(data.five);
}
}
jQuery(elements[i]).click({five:5},this.cellProp);
Try this : You can make use of $(this)
$(".config-cell").on("click", function(){
var clickedCell = $(this);// this refers to current clicked cell which can be used to get other details or write logic around it
});

Javascript events odd behavior, removeEventListener not working

I am trying to stop an event from being called multiple times by removing the event listener when I am done with it. (I need to be able to call these functions multiple times)
However, my current solution does not seem to remove the event listener from the "imageEvilRace", because when I call these functions a second time, "killRacer" ends up "killing" the same element that was the target the first time, as well as the new target. (killRacer deletes the same image that it targeted the first time when it should have no reference to that first image the second time it is called)
Here are the relevant functions reduced to the parts that are important for my question.
function killRacer() {
var listOfRaceImgs = getListOfRaceImages();
var killRacerTimerID = setInterval(moveEvilRaceImage.bind(null, randomRacer), 40);
imageEvilRace.addEventListener("stopKiller", stopMovingKillerImage.bind(null, killRacerTimerID, randomRacer));
}
function moveEvilRaceImage(imageToKill) {
imageEvilRace.dispatchEvent(stopKillerEvent);
}
function stopMovingKillerImage(killRacerTimerID, randomRacer) {
clearInterval(killRacerTimerID);
randomRacer.style.display = "none";
randomRacer.style.left = 0 + "px";
imageEvilRace.src = explosionImageSource;
imageEvilRace.removeEventListener("stopKiller", stopMovingKillerImage);
}
Your bound callback is stopMovingKillerImage.bind(null, killRacerTimerID, randomRacer) but you try to unbind stopMovingKillerImage. They're two different functions.
I would create a wrapper function to handle the storage of the correct function for a single-use event handler like this:
function bindOnce(object, event, callback) {
var new_callback = function() {
callback.apply(this, arguments);
object.removeEventListener(event, new_callback)
};
object.addEventListener(event, new_callback);
}
bindOnce(imageEvilRace, "stopKiller", stopMovingKillerImage.bind(null, killRacerTimerID, randomRacer));

Categories