I am trying to make a basic game on my website that involves you trying to prevent jellyfish pictures from reaching the top of the page by clicking on them to make them disappear. When I try to spawn in a jellyfish into this game however it deletes itself right away. What puzzles me more is that the javascript console logs that the element was deleted before it logs that an event handler was added. Is the jellyfish element somehow calling the delete event as soon as I set that attribute? To spawn the jellyfish on the page I click an element but the jellyfish spawn nowhere near this element if this is helpful. The playingfield parent is in a separate html file.
/*Code with the problem*/
var deleteJelly = function(jelly) {
var parent = document.getElementById("playingField");
var child = jelly;
parent.removeChild(child);
console.log("Jellyfish removed!")};
var spawnJelly = function(jellyType) {
jelliesSpawned++
var newJelly = document.createElement("img");
newJelly.setAttribute('src', "https://www.googledrive.com/host/0B-IaOP2CvHbffk56ZWFrUExfX1ZVNWZ0RmRmYU0tMHVoUHVDZzJ1NzhRV2l0c01kSENnNWc/jelly"+jellyType+".png");
document.getElementById("playingField").appendChild(newJelly);
newJelly.addEventListener("click", deleteJelly(newJelly));
console.log("added event listener")
};
/*Rest of code works fine*/
There is no need to pass jelly as the element calling the listener is this within the function. You can leverage that to simplify it to:
function deleteJelly() {
this.parentNode.removeChild(this);
console.log("Jellyfish removed!")
}
and setting the listener:
newJelly.addEventListener("click", deleteJelly);
which is a lot less code all round. ;-)
You've fallen for the classic "calling the function instead of passing the function" error.
newJelly.addEventListener("click", deleteJelly(newJelly)); // call function
vs
newJelly.addEventListener("click", function(){deleteJelly(newJelly);}); // pass function
It is deleted immediately because you have called the function, so it did what it is supposed to do: delete.
If instead you pass in an anonymous function that calls delete, then delete will not be called until the anonymous function is called - which is when you want it to happen.
Related
I am trying to build an chrome extension for lichess.org to permanently remove some elements from the website.
As the elements (in this case divs) can reappear if the user navigates through the website, I implemented a MutationObserver to remove the divs again as soon as they get added again. However, even though the function to remove them is called and they don't change their data-id, they are only removed when the function is called for the first time.
This is what I've tried so far:
These are the divs I want to remove. They are exactly the same after reappearing.
const bullet1 = document.querySelector('[data-id="1+0"]');
const bullet2 = document.querySelector('[data-id="2+1"]');
This is the MutationObserver. The div gets added with the addedNode.
const parent_lobby = document.querySelector("#main-wrap > main");
const mutationObserver = new MutationObserver(mutations => {
if (mutations[0].addedNodes[0].className == "lobby__app lobby__app-pools"){
remove_bullet_QP();
}
})
mutationObserver.observe(parent_lobby, {childList: true})
This is the function called to remove the elements. The first call of the method that happens as soon a the webiste is opened.
function remove_bullet_QP(){
bullet1.remove();
bullet2.remove();
}
remove_bullet_QP();
I've also tried to overwrite the divs before calling the function to remove them, but it didn't change the result.
Thank you for your help.
I changed bullet1 and bullet2 from const to let and reassigned them in every function call. This seems to work.
As I've said in my question, I've tried reassigning before, but without changing it to let. This should have given me an error message, somehow it didn't.
I keep reading that there is no difference between the two but I am writing a quiz app and am seeing some differences
For example:
Quiz.prototype.handleGuess = function (id, guess) {
let button = document.getElementById(id);
button.addEventListener('click', function(){
quiz.guess(guess);
console.log(guess);
}
if(!quiz.hasEnded){
quiz.displayNext();
} else {
quiz.displayScore();
};
};
When using an event listener, I will log the guess to the console on the first button choice. When I choose an answer for the second question the console will read not only the new selection but also the choice I made from the previous question. This does not happen when using .onclick() and I am not sure why!
Consider the following code:
var el1 = document.getElementById("someEl1"),
el2 = document.getElementById("someEl2");
function firstHandler() {
alert("First handler");
}
function secondHandler() {
alert("Second handler");
}
el1.addEventListener("click", firstHandler);
el1.addEventListener("click", secondHandler);
el2.onclick = firstHandler;
el2.onclick = secondHandler;
<div id="someEl1">First Element</div>
<div id="someEl2">Second Element</div>
In case 1, clicking on el1 will alert with both messages. In case 2, clicking on el2 will only alert with the second because we overwrote what onclick does with the second assignment.
addEventListener effectively assigns a callback to some internal array of listener callbacks that will all be called whenever the event is triggered.
onclick is a single property with a single value. When you assign to it, the old value is replaced by the new assignment.
I would highly suggest that you do not use the onclick method. It makes code harder to maintain. If you are in a large code base and you set the onclick of an element and then later on another coder also sets the onclick without knowing that that element already had its onclick set, then you will run into a difficult time trying to figure out why your code is broken all of a sudden. Using the event listener pattern makes for more extensible and decoupled code.
This code randomly plays Audio elements. Running setup twice allows you to do this with two different arrays simultaneously, which is what I want. The problem is that #stop only stops one of the arrays playing. This actually also happens if you only call setup on one array, but click #start more than once (which I also don't want). I figure this has to do with 'intervalReturn', as it would only be specified to one setInterval.
How should I write this so that multiple invocations of setup creates distinct setIntervals which can be started only once?
Alternately, if I should approach this from a totally different angle, what would be better?
EDIT: This was fixed per the suggestions below. But I'm wondering, what is going on "under the hood" with setInterval here? Why does this behavior happen at all? (Specifically: #stop stops one but not all audio elements.)
var CMajor3 = new Array ("C3","D3","E3","F3","G3","A3","B3","C4a");
var CMajor4 = new Array ("C4b","D4","E4","F4","G4","A4","B4","C5");
var intervalReturn = null;
function pickAndPlay(pitchSet){
fyshuffle (pitchSet); // the Fischer-Yates shuffle function
var tone = document.getElementById(pitchSet[0]);
tone.currentTime = 0;
tone.play();
};
function setup(time, pitchSet){
$("#start").click(function() {
console.log("startClicked");
intervalReturn = window.setInterval(pickAndPlay, time, pitchSet)
});
$("#stop").click(function() {
console.log("stopClicked");
window.clearInterval(intervalReturn)
});
};
$(document).ready(function() {
setup(2000, CMajor3);
setup(2000, CMajor4);
});
But I'm wondering, what is going on "under the hood" with setInterval here?
Each time you call setup(), that creates additional click handlers on the #start and #stop elements. When you actually click #start, or #stop, all of the applicable handlers are called (in the same order they were bound). This is why clicking #start causes both CMajor3 and CMajor4 notes to play. You get multiple concurrent but unrelated intervals running.
With intervalReturn defined as a global variable, you only ever have the interval ID that was returned from the most recent call to setInterval(), because each time the #start click handler runs it overwrites the previous one. That's why clicking #stop only ever stops one of the intervals and there is no way to stop the others.
Moving the var intervalReturn declaration inside the setup() function helps because the way closures work in JS is that the arguments and local variables of setup(), i.e., time, pitchSet and intervalReturn (after you've moved the declaration) are accessible in the two event handlers defined in the current call to setup(). Subsequent calls to setup() create new closures with their own separate copies of those variables. So then the #stop click handler uses the individual intervalReturn relevant to its own setup(). And since both #stop handlers run, both intervals get cleared.
But you still have the problem that clicking #start# more than once without clicking #stop creates additional intervals and then within any one setup() that individual intervalReturn gets overwritten with the latest, so again #stop has no way to refer back to the previous intervals. Which is why adding if (intervalReturn === null) in the #start handler helps to only start a new interval if there is not already one running. (And then you need to add intervalReturn = null in the #stop handler because just calling clearInterval(intervalReturn) doesn't change the value of the intervalReturn variable.)
Suggestion: Update your existing console.log() statements to the following:
console.log("startClicked", pitchSet, intervalReturn);
// and
console.log("stopClicked", pitchSet, intervalReturn);
And maybe more the startClicked one to just after calling setInterval() so that it logs the interval ID that was just returned, not the previous one. That way you can see the values of all of the relevant variables and see what is happening.
I'm trying to execute JavaScript functions that are called when a event (for example onClick event) is performed on a web page with JavaScript code. I'm getting the function from the event like this :
var attributval = document.getElementsByTagName("a").getAttribute('onClick');
and I'm trying to execute this object (which a JavaScript function in fact) as a function (suppose we have <a onClick = alert('whatever');> on this example, I tried:
var attributval = document.getElementsByTagName("a").getAttribute('onClick');
attributval() = function(){attributval};
attributval();
but it didn't work.
A DOM attribute is not the same as a JavaScript property (even though they can have the same name onclick). You should use
var attributval = document.getElementsByTagName("a")[0].onclick;
to retrieve a function (or null) from the JS object (as opposed to getAttribute(), which will most likely return a toString() for the property).
Now, attributval() = is illegal syntax, as attributval() is not an l-value (you cannot assign to it).
attributval(); will work but without the second line (which is illegal JavaScript) it will invoke the original A element onclick handler (if one is defined) or throw an exception (if the onclick handler is null).
Skip trying to create a function around the function. Just call it:
var attributval = document.getElementsByTagName("a")[0].onclick;
attributval();
try
var attributval = document.getElementsByTagName("a")[0].getAttribute('onClick');
By using get attribute you are returning a string so your only way is to use eval(onclickString) or var fn = new Function(onClickString); fn();
attributval is simply a string, correct? If you trust this code, execute it with eval(attributval) -- however any reference to this won't work.
What you probably want is to manually trigger an event. jQuery makes that easy.
If you want to do more than a click, then Chris McDonald's answer at Is it possible to trigger a link's (or any element's) click event through JavaScript? seems to fit the bill, although you might need to heed the third comment.
I thought I'd add a short answer on how to work with events using jQuery, since it seems relevant.
// Select the link using it's ID field (assuming it has one)
var myLink = $('a#myLink')
// Add a click event to the link
myLink.on('click', function(e) {
console.log("I've been clicked!");
});
// Trigger the click event manually. This would result in the above
// function being run. Interestingly, this will not cause the browser
// to follow the link like a real click would
myLink.trigger('click');
// Remove the click event (this removes ALL click events)
myLink.off('click');
// Add a click event to the link that only runs once, then removes itself
myLink.one('click', function() {
alert("I'll only bother you once!");
});
// Add a click event that you can identify from other click events.
// This means that you can trigger it or remove it without bothering other
// click events
myLink.on('click.myClick', function() {
alert("This click event has been identified as 'myClick'");
});
// Now you can trigger it without triggering other click events
myLink.trigger('click.myClick');
// And remove it, also with no harm coming to other click events
myLink.off('click.myClick');
Hope this helps
I have a problem with my code, some code does not work when I call recursive call of same function inside it. It has to be something with myGalleria = Galleria.get(0);, but I have no idea how to make it all work.
Document Ready (just to show when I call function for the first time, everything works fine for first time)
$(document).ready(function(){
$.getJSON('getImages.php', {
cat: "123"
}, function(imgData){
createGallery(imgData);
});
});
Now function itself, note that when I click on .galleria-menuButtons span that calls same function nothing is working, well galleria itself is creating, but nothing else.
function createGallery(imgData){
$("#gallery").galleria({
image_margin: 30,
clicknext: true,
transition: "fade",
dataSource: imgData
});
myGalleria = Galleria.get(0); // I don't think this works after recursive call
// Adding menu and menu buttons
myGalleria.addElement("menu").appendChild("container", "menu");
myGalleria.addElement("menuButtons").appendChild("menu", "menuButtons");
$.ajax({
url: "menuButtons.php",
success: function(data){
myGalleria.$("menuButtons").html(data);
}
});
// Menu button click events
$('.galleria-menuButtons span').live('click', function(){
alert(this.id);
// Getting jSon data
$.getJSON('getImages.php', {
cat: this.id
}, function(imgData) {
alert(imgData);
createGallery(imgData); // <- Recursive call
});
});
}
I have similar function on window.resize and it also does not work after recursive call.
$(window).resize(function(){
$(".galleria-container").css("width", $(window).width());
$(".galleria-container").css("height", $(window).height());
galleriaRescale = Galleria.get(0);
galleriaRescale.rescale(); // <- this is not working either
//sizeBG();
});
FYI - this isn't actually recursion in the traditional sense because you're calling createGallery from a click handler which launches a JSON request which then calls createGallery when that succeeds, both of which will occur after the previous call to createGallery finishes.
But you do have surviving function closures which could be confusing things or causing problems. One guess is that you may want to make sure that things you expect to be local variables (like myGalleria have a var in front of them so they really are local variables and not variables that might be scoped to a higher level and be influenced by a previous incarnation of this call or be influencing an earlier call that hasn't yet completed.
var myGalleria = Galleria.get(0);
Then, assuming imgData is some sort of data structure like an array or object, you have to make sure that there's either only one global version of that data structure that never changes or that each call of createGallery has the appropriate separate copy of that data structure. If it's getting changed along the way, then subsequent calls to createGallery may not be getting the data they want. If it's a read-only data structure (you don't change it), then you're probably OK on that one.
OK, let's talk through the pseudo code for what this does.
On page ready, you get some JSON image data.
When that succeeds, you call createGallery with that image data.
The createGallery call does some sort of operation in the DOM (perhaps an animation)
It then calls: myGalleria = Galleria.get(0); Because there is no var in front of myGalleria, this is a global variable declaration (bad news for recursion and closures)
You then use the myGalleria data structure to make some changes to the DOM (adding menus and menu items).
You then add a .live click handler on a pretty generic set of CSS classes (it's possible you have added this click handler more than once here).
You then fetch some JSON image data again.
When that image data is fetched, you start the whole process over again by called createGallery.
Summary
The two potential problems I see are that myGalleria is not a local variable and probably should be and you may be adding duplicate click handlers.
If neither of these fully solve the issue, then we probably need more information about what Galleria.get(0) is doing.
FYI, the resize clickHandler looks like it may have the same issue with not using var to make your variable declaration a local variable.
Round 2
OK, here are some more observations.
When you add the menu and menu buttons with this block of code, you aren't providing any unique identifiers to either the addElement or appendChild functions (you're providing "menu" and "menuButtons" to both). As such, I don't know how you can uniquely hook up to them in the subsequent click event. As far as your code looks, all the menu items look identical and none have unique state. I don't know the Galleria code, but I assume somebody has to make unique identifiers for these new items so that you can uniquely identify them in your subsequent click handler.
// Adding menu and menu buttons
myGalleria.addElement("menu").appendChild("container", "menu");
myGalleria.addElement("menuButtons").appendChild("menu", "menuButtons");
When you set up a click handler to presumably handle the clicks for just these menu items, you are using the exact same CSS selector every time so there's no way that this click handler is going to be uniquely assigned to just the newly create menu items (which is what I assume you want). I don't know the Galleria code, but I assume that you should create some sort of unique ID that you pass into addElement and appendChild for the newly created menu items and then reference that unique identifier when you install the click handler. Likewise, this function needs to uniquely target just the menu buttons you created by using unique identifiers myGalleria.$("menuButtons").html(data);
Lastly, I'd suggest you change the name of one of your variables just to avoid confusion. In your click handler, change the three occurrences of imgData to just data so there can be no confusion about closures and the value of imgData.
Round 3
Ultimately one of the fixes was this (embedded in the comments):
I think it might work if you just only install the .live click handler once outside the createGallery function rather than call it each time. Since it's .live it will automatically work for all future buttons you create so you should only call it once. I'd suggest putting it in the $(document).ready function block.