javascript unbind event not unbinding even though it fired - javascript

I have some code that when called inside an object, binds an element on the page to a click. When I press a start button again, the elements are updated and I'm adding an event listener for click again. I'm unbinding the event listener prior to adding the even listener again, it fires 10 times to unbind... and then I bind, and it fires 10 times for the binding to be readded... BUT. when I inspect the element, or click the element, it fires twice. If I hit start again, it now fires three times...
card.clickFn = function() {
if(Game.cardsClickedHistory.length<2 && Game.status==1){
card.element.style.backgroundImage = card.image;
Game.cardClicked(card); // notify a card clicked
}
}
card.element.removeEventListener("click", card.clickFn);
card.element.addEventListener("click", card.clickFn);
Repeatable examples
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
.card {width: 200px; height: 200px; background: red; margin: 10px;}
</style>
</head>
<body>
<div id="cards">
<div class="card"></div>
<div class="card"></div>
</div>
<button id="rebind">REBIND</button>
<script>
var Card = function(element){
var card = this;
card.element = element;
card.clickFn = function(){
console.log("Clicked card");
}
card.element.removeEventListener("click", card.clickFn);
card.element.addEventListener("click", card.clickFn);
}
var CardFactory = function()
{
var cf = this;
cf.cardDivs = document.getElementsByClassName("card");
cf.build = function(){
for(var i=0;i<cf.cardDivs.length; i++)
{
new Card(cf.cardDivs[i]);
}
}
}
var oCF = new CardFactory();
oCF.build();
document.getElementById("rebind").addEventListener("click", oCF.build)
</script>
</body>
</html>

When you instantiate Card object by calling constructor new Card(cf.cardDivs[i]); , the constructor create new function that is not previous function in last construction. Therefore when it tries to remove listener, obviously can not find the previous listener. To Avoid this, you could set a static variable (listeners) that stores bound listeners. Then on next cal of build() function easily can find exact recorded previous bound function as listener. I added a little bit features in your code that it works.
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
.card {width: 200px; height: 200px; background: red; margin: 10px;}
</style>
</head>
<body>
<div id="cards">
<div class="card"></div>
<div class="card"></div>
</div>
<button id="rebind">REBIND</button>
<script>
var Card = function(element){
var card = this;
card.element = element;
card.clickFn = function(){
console.log("Clicked card");
}
var listener=Card.listeners.filter(function(lis){ return lis.ele===element })[0];
if(listener!==undefined){
card.element.removeEventListener("click", listener.func);
Card.listeners=Card.listeners.filter(function(lis){return lis.ele!==element}); // remove previous listener record
}
card.element.addEventListener("click", card.clickFn);
Card.listeners.push({ele:element,func:card.clickFn}); // add record of listener
}
Card.listeners=[]; //static member
var CardFactory = function()
{
var cf = this;
cf.cardDivs = document.getElementsByClassName("card");
cf.build = function(){
for(var i=0;i<cf.cardDivs.length; i++)
{
new Card(cf.cardDivs[i]);
}
}
}
var oCF = new CardFactory();
oCF.build();
document.getElementById("rebind").addEventListener("click", oCF.build)
</script>
</body>
</html>

Related

JS: calling functions via buttons does not work anymore, if page is updated without reloading the page

I want to write a program with one html page, which i can update and fill with different elements via javascript with one button that stays the same in every version, which displays a modalBox. I made a very basic version of this: One page, that is filled with two buttons (next and last) for navigating through the pages and one to display the modal. In addition, i added a number, which is incremented oder decremented accordingly, when you click through the updated versions of the page.
var counter = 1;
function setUp(){
var c = document.getElementById("container");
var d = document.createElement("div");
d.setAttribute("id", "main");
d.innerHTML = counter;
var nxt = document.createElement("button");
var bck = document.createElement("button");
var modalBtn = document.createElement("button");
nxt.innerText = ">";
bck.innerText = "<";
modalBtn.innerText="Show Modal";
nxt.setAttribute("onclick","nextPage()");
bck.setAttribute("onclick","lastPage()");
modalBtn.setAttribute("onclick","showModal()");
d.appendChild(bck);
c.appendChild(d);
d.appendChild(nxt);
d.appendChild(modalBtn);
}
function showModal(){
var m = document.getElementById("modal");
m.style.display = "block";
}
function closeModal(){
var m = document.getElementById("modal");
m.style.display = "none";
}
function nextPage(){
var c = document.getElementById("container");
c.innerHTML="";
counter++;
setUp();
}
function lastPage(){
var c = document.getElementById("container");
c.innerHTML="";
counter--;
setUp();
}
setUp();
*{
padding: 0;
margin: 0;
}
#modal{
position: absolute;
width: 500px;
height: 300px;
background-color: black;
display: none;
}
#main{
background-color: aliceblue;
height: 500px;
width: 800px;
}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="tryout.css">
<script src="tryout.js" defer></script>
<title>Document</title>
</head>
<body>
<div id="container">
<div id="modal"><button id="closeButton" onclick="closeModal()">Close</button></div>
</div>
</body>
</html>
The problem is: on onload, the modal button works fine (on click, the modal is displayed). As soon as i update (not reloading!) the page via the next- or back button, the modal button stops working (Error-message says the type of modalbutton is null). I have no clue why, because to my knowledge, the buttons are reinitiated by clicking on the next or back button (because the setUp()-function is called in the functions triggered by the buttons). As soon as I reload the page via the reload-button, it is working until i use one of the next and back buttons.
I am new to js, it's probable that I'm missing sth. obvious here :) Many Thanks!
On the "nextPage" and "lastPage" function , you're just removing the whole content (including the modal) from the div which associated container class.
That's why when you calling the "showModal" function , there is no element with modal id on the DOM. That's why its saying null.
you can follow what Sakil said on the comment or, in your both(nextPage & lastPage) functions,you can just remove the div with id main and add it later on "setUp" function.
I'm adding some code snippet below, hope it will help;
function nextPage() {
//grab the div with "main" id
let main = document.getElementById("main");
//remove it from document
main.remove();
counter++;
//add it again; what you're doing actually.
setUp();
}
function lastPage() {
//grab the div with "main" id
let main = document.getElementById("main");
//remove it from document
main.remove();
counter--;
//add it again; what you're doing actually.
setUp();
}
of course you should refactor the code base, cause there is lot more copy-pasting staff present, but I'm leaving that up to you.

Javascript loading speed issue as variable increases

I am having an issue with page loading time. Currently right now I am running UBUNTU in Oracle Vm Virtual Box. I am using mozilla firefox as my browser and I am working on an etchasketch project from "The odin project".
My problem is the page loading time. The code takes a prompt at the start and generates a grid for the etch a sketch based on that prompt. I have not given it the minimum and maximum values (16 and 64) respectively, however any number when prompted at the beginning that is beyond 35 doesn't load or takes ages to load.
How do I speed up the process time? / why is it moving so slow? / how can I avoid this ? / is there a fix that I am over looking that can make this work a lot faster? / feel free to tackle any and all of those questions!
This is my HTML CODE:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8"/>
<title>
</title>
</head>
<body>
<div class="etchhead">
<p> Choose your grid size </p>
<input type = "text"></input>
<button id="startOver"> Clear Grid </button>
<p> Change color </p>
</div>
<div id="grid">
</div>
<script src="eas.js"></script>
</body>
</html>
And this is my CSS code:
p {
color: blue;
display: inline;
}
#grid {
display: grid;
width: 800px;
max-width: 800px;
height: 800px;
max-height: 800px;
line-height: 0;
}
.gridBox {
border: 1px solid black;
background-color: lightgrey
}
And this is my JAVASCRIPT code:
gridStart();
function gridStart(){
var boxes = 0
var selectBody = document.querySelector("#grid");
var addBox = document.createElement("div");
var boxCountStart = prompt("enter a number between 16 and 64");
var boxDimensions = (boxCountStart * boxCountStart);
function rowsAndColumns() {
var selectBody = document.querySelector("#grid");
var gridTemplateColumns = 'repeat('+boxCountStart+', 1fr)';
selectBody.style.gridTemplateColumns= gridTemplateColumns;
selectBody.style.gridTemplateRows= gridTemplateColumns;
};
function hoverColor(){
var divSelector = selectBody.querySelectorAll("div");
divSelector.forEach((div) => {
div.addEventListener("mouseover", (event) => {
event.target.style.backgroundColor = "grey";
});
});
};
rowsAndColumns();
for (boxes = 0; boxes < boxDimensions ; boxes++) {
var selectBody = document.querySelector("#grid");
var addBox = document.createElement("div");
addBox.classList.add("gridBox");
addBox.textContent = (" ");
selectBody.appendChild(addBox);
hoverColor();
};
};
There are two components to your issue. One is that you are repeatedly modifying the DOM in a loop. You can fix it by appending all your boxes to a DocumentFragment and then adding that to the DOM after your loop finishes. You are also calling hoverColor(); inside your loop which results in adding tons of event listeners that all do the same thing (since inside hoverColor you are adding a listener to every single div). You can fix both those issues like this:
var fragment = document.createDocumentFragment( );
for (var i = 0; i < boxDimensions ; i++) {
var addBox = document.createElement("div");
addBox.classList.add("gridBox");
addBox.textContent = (" ");
fragment.appendChild(addBox);
}
document.querySelector("#grid").appendChild( fragment );
hoverColor();
Here is a JSFiddle with your original code, and here is one with the modification.
You could also benefit from only having one event listener total. You don't need to loop and add an event listener to every div. Just add one to #grid and use event.target (like you already do, to find the div that the event originated from). Something like this:
function hoverColor(){
document.querySelector("#grid").addEventListener( 'mouseover', function ( event ) {
event.target.style.backgroundColor = "grey";
} );
}

Chrome - Performance Monitor - DOM Nodes keep on stacking

I'm trying to get my head around - why is the DOM Nodes keeps on going up when I'm checking my website in the Performance Monitor.
I've added this simple code that just looping on:
1) adding and element to a wrap,
2) bind it with a click event.
3) removing it.
but still the DOM Nodes are always up the I check the performance.
any thoughts?
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
.wrap{
font-size:50px;
}
</style>
</head>
<body >
<div class="wrap">
<div></div>
</div>
<script>
var counter = 0;
setInterval(function () {
//increase counter (for display)
counter++;
//get wrap
var wrap = document.getElementsByClassName("wrap")[0];
//remove its children
while (wrap.firstChild) {
wrap.firstChild.removeEventListener("click", onclick);
wrap.removeChild(wrap.firstChild);
}
//create new element
var div = document.createElement("div"); // create a div element
div.innerHTML = 'hello mosh (' + counter + ')';
//bind events
div.addEventListener("click", onclick);
// append the div to wrap
wrap.appendChild(div);
}, 200);
//on click function
var onclick = function () { alert('click'); }
</script>
</body>
</html>
The v8 engine is garbage-collected, removed DOM elements are not destroyed immediately.
Occasionally unused (unreachable) objects are garbage-collected.
If I wait long enough, with your code, I do see DOM Nodes go back down to the original value (had to bump the interval to 20 to speed up the process).
For this reason it is often more efficient to not remove DOM elements, and instead just replace the HTML content.
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
.wrap2 {
font-size:50px;
}
</style>
</head>
<body >
<div class="wrap2">
<div></div>
</div>
<script>
var counter = 0;
var wrap = document.getElementsByClassName("wrap2")[0];
var div = wrap.getElementsByTagName("div")[0];
setInterval(function () {
counter++;
div.innerText = 'hello mosh (' + counter + ')';
}, 20);
//on click function
var onclick = function () { alert('click'); }
</script>
</body>
</html>

How do I make a JavaScript created list item clickable?

I've been going through all the related questions but none of the solutions have been working for me. I am extremely new to JavaScript and I'm confused as to how to make a list that I created with JavaScript have clickable items. The most recent attempt included attempting to make an alert pop up on click but instead it just pops up the second the page loads. Please help! Here is my current code:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="css/m-buttons.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
div.links{
margin: auto;
border: 3px solid #003366;
text-align: left;
max-width: 700px;
}
p{
font-size: 40px;
text-align: center;
}
li{
font-size: 1w;
}
body{
font-family: verdana;
}
</style>
</head>
<body>
<div class = "links">
<ul id="blah"></ul>
<script>
var testArray = ["One","Two","Three","Four","Five","Six","Seven"];
function makeLongArray(array){
for(var i = 0; i < 1000; i++){
array.push(i);
}
}
makeLongArray(testArray);
function makeUL(array) {
// Create the list element:
var list = document.createElement("UL");
list.setAttribute("id", "blah");
for(var i = 0; i < array.length; i++) {
// Create the list item:
var item = document.createElement("LI");
// Set its contents:
item.appendChild(document.createTextNode(array[i]));
// Add it to the list:
list.appendChild(item);
list.onclick = alert("Help."); //this is the code causing the issue.
}
// Finally, return the constructed list:
return list;
}
// Add the contents of options[0] to #foo:
document.getElementById("blah").appendChild(makeUL(testArray));
</script>
</div>
</body>
</html>
Your existing code will execute alert('Help.') every time you execute this line of code list.onclick = alert("Help.");
What you need to do is assign a function to onclick. This function then get executed when onclick is executed. As follows:
item.onclick = function() {console.log('hello world');};
Now each list item's onclick event has a function assigned to it that outputs hello world to the console everytime the list item is clicked.
You need to assign a function to the onclick event:
list.onclick = function(){ alert("Help."); }
Here is my solution:
function toggleDone(event) {
var ev = event.target;
ev.classList.toggle("done");
}
ul.addEventListener("click", toggleDone);

How to show a div what was hidden on img

So I have to make a hidden element (div) visible when you click on an image.
It worked on a button, but I can't seem to make it work on an image.
HTML code:
<div id="img4">
<img src="images/nummer1.png" alt="nummer1" data-hint="1"/>
</div>
<div id="hint" class="hidden">Hint 1</div>
JavaScript code:
window.onload = function () {
'use strict';
var button = document.getElementById("button");
var hint = document.getElementById("hint");
var showHint = function (event) {
console.log(this);
if data-hint === '1'
hint1.classList.toggle('hidden');
};
var img = document.querySelectorAll('img');
console.log(img);
for (var i = 0; i < img.length; i++) {
img[i].addEventListener("click", showHint);
};
function show(hint) {
};
button.addEventListener('click', show);
You see? It works on the button, but for some reason I can't make it work on an Image.
I notice you're not using jQuery. You might want to look into it, as it would make what you're trying to implement easier and much more readable.
But in any event:
hint1.classList.toggle('hidden');
Should be
hint.classList.toggle('hidden');
Also,
if data-hint === '1'
Needs to be
if (this.dataset.hint == 1)
which is how you check the data-hint attribute of this element (the one that was clicked).
http://jsfiddle.net/gunderjt/t46ws/
Should work:
$("img").click(function(){
$("div").show();
});
var showHint = function (event) {
console.log(this);
if(typeOf(hint) != 'null') hint.classList.toggle('hidden');
};
Actually you should update the hints id:s so for every image there are a corresponding hint, which shares the same id. So the first hint got a id of "hint-1".
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<style>
.hidden {
display: none;
}
.show {
display: block;
}
</style>
<div id="img4">
<img src="/test/img2.jpg" alt="nummer1" data-hint="1"/>
</div>
<div id="hint-1" class="hidden">Hint 1</div>
<script type="text/javascript">
window.onload = function() {
var images = document.getElementsByTagName("img");
for(var i=0;i<images.length;i++){
images[i].onclick = function() {
var that = this;
var id = that.getAttribute("data-hint");
var hint = document.getElementById("hint-" + id);
hint.className = "show";
};
}
};
</script>
</body>
</html>

Categories