Event listener, classes and garbage collection - javascript

I just wanted to clear something up in my own head.
I have a Class with methods generating html. I also have a method generating an eventlistener.
I find the event listener only works once (when the button is pushed in this instance). I checked the developer tools and the event listener is removed.
it only stays and works more than once when I make the eventlistener callback recursive so the eventlistener is reapplied every callback.... like below
calculate(){ //class method
let button_html = document.getElementById("calculateBut");// probably quite leaky finding this twice
button_html.addEventListener("click", this.calculate.bind(this));// recursive binding of event listener and callback. need this because i think whats happening is the callback is being removed by garbage collection after the render_content() method is finished
}
//called in another part of the class
let button_html = document.getElementById("calculateBut"); //getting the calculate button after its creation
button_html.addEventListener("click", this.calculate.bind(this)); // binding call back function to scope of the class. not the callback function internal scope
I am fairly sure it either because of the global execution context being deleted off the call stack or something around garbage collection but please if im missing something please let me know
also if you think be should be doing thing differently, please let me know.
thanks is advance!
edit:
Full classes & main below:
Main
let toolPage = new TablePage(toolBut, homeUrl ,tool_dataUrl, tools_title);
let materialsPage = new TablePage(materialsBut,homeUrl, material_dataUrl ,materials_title);
let feedsSpeedsPg = new CalcPage(feedSpeedsBut, homeUrl, [tool_dataUrl, material_dataUrl], FS_title);
//event listeners - have url hash operations for use later
homeBut.addEventListener("click", function(){window.location = "/";});//eventlistener for clicking te home button
feedSpeedsBut.addEventListener("click", function(){ //eventlistener for clicking te F&S button
window.location.hash = feedSpeedsBut.innerHTML
feedsSpeedsPg.render_content();
});
materialsBut.addEventListener("click", () => { //eventlistener for clicking te materials button
materialsPage.render_content();
});
toolBut.addEventListener("click", () => { //eventlistener for clicking te tools button
toolPage.render_content();
});
CalcPage
generate_results(){ // create a container for results
this.content_html.innerHTML += `<div id = "results"></div>`;
}
populate_results(fr,sd){ //populate the results container
let results_html = document.getElementById("results");
results_html.innerHTML = "";
let feed_rate = `<div id = "feed_rate">${"Feed Rate = " + fr + "mm/min"}</div>`;
let stepdown = `<div id = "stepdown">${"Step Down = " + sd + "mm"}</div>`;
results_html.innerHTML = feed_rate + "\n" + stepdown;
}
generate_calculate_button(){ //generate button
let button = `<button type="button" id="calculateBut"> Calculate </button>`
return button;
}
calculate(){
let button_html = document.getElementById("calculateBut");// probably quite leaky finding this twice
button_html.addEventListener("click", this.calculate.bind(this));// recursive binding of event listener and callback. need this because i think whats happening is the callback is being removed by garbage collection after the render_content() method is finished
}
async generatePage(populate_dropdown, generate_calculate_button){
//wiping previous page
this.table_html.innerHTML = "";
this.title_html.innerHTML = this.title;
//injecting dd into html
this.content_html.innerHTML = material_dropdown + "\n" + tool_diameter_dropdown + "\n" + tool_flute_number + "\n" + RPM_dropdown + "\n" + generate_calculate_button();
this.generate_results();// generate results container after page load could follow convention of insertion as the line above but good to know eithe way works
let button_html = document.getElementById("calculateBut"); //getting the calculate button after its creation
button_html.addEventListener("click", this.calculate.bind(this)); // binding call back function to scope of the class. not the callback function internal scope
}
render_content(){
this.generatePage(this.populate_dropdown,this.generate_calculate_button);
}
}

Related

Why my Chrome extension event doesn't work?

I'm trying to create a chrome extension. I had a problem with the affectation of event for the new element that i append to the dom of site with content. Js
If I add an event to an element' 'for example class' exist already in the page, it works correctly. Just for my new appended element((in the code iadded a button ,the event is just an alert to test))
function tst() {
myclass = $("._3hg-._42ft");
myclass = myclass.not(".supp");
myclass.addClass("supp");
var patt = /https:\/\/(.)*\.facebook\.com\/(.)*\/(posts|photos|videos)\/(\w|\.|\d)*/g;
for (i = 0; i < myclass.length; i++) {
result = patt.exec(myclass[i]);
myclass.append('<button class="fact" id=' + result[0] + ' style="position: absolute;">fact</button>');
};
/* this is a simple event*/
/***********************/
$(".fact").on('click', function() {
alert("no event work ");
});
Making somewhat broad assumption here in my answer that it is JavaScript/jQuery related and is NOT an extension...or is so still in that context.
You need to attach the event to the container here perhaps for the dynamically created elements. Lots of global stuff, suggested to not do that, updated there.
Appends a lot of buttons perhaps? might need to only hit DOM once but left as-is in this isolated function.
function tst() {
let myclass = $("._3hg-._42ft")
.not(".supp");
myclass.addClass("supp");
//let result = {};
var patt = /https:\/\/(.)*\.facebook\.com\/(.)*\/(posts|photos|videos)\/(\w|\.|\d)*/g;
var i = 0; //avoid global
for (i; i < myclass.length; i++) {
// broad assumption of the returned value from patt.exec() here
// not even sure why it needs an id, have a class, use for css
let result = patt.exec(myclass[i]);
myclass.append('<button class="fact" id="' + result[0] + '">fact</button>');
}
/* attache event to pre-existing element */
/***********************/
myclass.on('click', ".fact", function() {
alert("event works");
});
}
button.fact {
position: absolute;
}

JS: Attach a clicklistener when generating a select-option

I'm working on a small project with the object of creating an online pizza delivery service have Element called pizzaPreviewList, which gets filled with data from an Array pizzas.
Now I want to delete an Item in pizzaPreviewList, when it is clicked. My approach was as follows.
Shopping cart renders:
self.render = function () {
self.pizzaPreviewElement.innerHTML = "";
for(var k in self.pizzas) {
var element = self.pizzas[k].getElement();
self.pizzaPreviewElement.appendChild(element);
}
};
Pizza creates new Option Item in get Element():
self.getElement = function () {
var elementContainer = document.createElement('option');
elementContainer.innerHTML = '<span>'+ self.name + ' ' + self.preis.toFixed(2) + '€</span>';
elementContainer.addEventListener('click', onClick());
return elementContainer;
};
onClick is then supposed to run a method that removes the Pizza and rerenders, but onClick is automatically called when the element is created and my Array goes nuts, because it tries to delete things that are not there yet. How do I solve this?
No use of Frameworks is allowed.
As I can see, you are appending the elements dynamically to the page. But the event handler(elementContainer.addEventListener('click', onClick());) is added before the element is rendered on the page.
So the thing with dynamic elements is that, you have to add its handlers dynamically too.
To do this, simply change your lines from this-
var elementContainer = document.createElement('option');
elementContainer.innerHTML = '<span>'+ self.name + ' ' + self.preis.toFixed(2) + '€</span>';
elementContainer.addEventListener('click', onClick());
into this line -
var elementContainer = '<option onclick="onClick()"><span>'+ self.name + ' '+self.preis.toFixed(2) + '€<span><option>';
UPDATE
then you can modify your render() function and put the returned string in the self.pizzaPreviewElement.innerHTML instead of using self.pizzaPreviewElement.appendChild(element);
Just make sure that the function onClick() exixts before you append the element to the page.

Javascript onclick parameter

I have a question about "onclick" function in JavaScript. Here I have a div "InfoBar"
<div id="InfoBar"><br>
and two for loop
var src = new Array();
for(var i = 0; i < 2; i++){
src.push("el1","el2");
}
for(var j = 0; j < 2; j++){
doesFileExist(src[j]);
}
and a doesFileExist() and klick function
function klick(el){
alert(el)
}
function doesFileExist(urlToFile){
document.getElementById('InfoBar').innerHTML += '<br>' + '<a id="css" onclick="klick(urlToFile)" href="#" title="'+urlToFile+'">' + "link1 : " + urlToFile + '</a>';
}
now I've added a "onclick" function in "a href".
if I click on "link1:el1", I want to display as alert "urlToFile" string.
But I doesn't work.
In "a href" title="'+urlToFile+'" it works perfect, but in "onclick" doesn't work.
Can anyone help me?
Thanks in advance.
You are generating an attribute. That gets converted back into a function but the scope is broken.
Don't use intrinsic event attributes.
Minimise use of globals
Avoid generating HTML by mashing strings together (at best it is hard to read, at worst you get this sort of issue)
Use standard DOM:
var container = document.getElementById('InfoBar');
container.innerHTML = ""; // Delete any existing content
container.appendChild(document.createElement('br'));
var anchor = document.createElement('a');
anchor.setAttribute('id', 'css'); // You are running this function is a loop and creating duplicate ids. Use a class instead.
anchor.addEventListener('click', function (event) {
klick(urlToFile); // the local variable urlToFile is still in scope
});
anchor.setAttribute('href', '#'); // Why are you linking to the top of the page? Use a <button>
anchor.setAttribute('title', urlToFile);
anchor.appendChild(document.createTextNode("link1 : " + urToFile));
container.appendChild(anchor);
Event handles assigned this way won't work. You have to use JavaScript event handles. Means, you must create a new 'a' element, then bind a click event to it, and then append it as a child to the parent node. All this stuff is very good described on the web out there.

Using this within functions called with onclick event in Javascript

I'm currently building a small Todo list application using vanilla Javascript but I'm having some issues creating a delete button that onClick removes it's parent element.
From what I have read, when an onClick is called in Javascript the this keyword can be used to refer to the element that called the function. With this in mind I have the following code:
window.onload = initialiseTodo;
function addRecord(){
var title = document.getElementById('issueTitle');
var issueContent = document.getElementById('issueContent');
var contentArea = document.getElementById('contentArea');
if(title.value.length > 0 && issueContent.value.length > 0){
var newItem = document.createElement('div');
newItem.id = 'task' + count++;
newItem.className = 'task';
newItem.innerHTML = '<div class="taskbody"><h1>' + title.value + '</h1>'+ issueContent.value + '</div><div class="deleteContainer">'
+ '<a class="delete">DELETE</a></div>';
contentArea.appendChild(newItem);
assignDeleteOnclick();
}
}
function deleteRecord(){
this.parentNode.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode);
}
function assignDeleteOnclick(){
var deleteArray = document.getElementsByClassName('delete');
for(var i=0;i<deleteArray.length;i++){
deleteArray[i].onclick= deleteRecord();
}
}
function initialiseTodo(){
var btn_addRecord = document.getElementById('addRecord');
btn_addRecord.onclick = addRecord;
}
Basically I have a form that has two fields. When these fields are filled and the addRecord button is clicked a new div is added at the bottom of the page. This div contains a delete button. After the creation of this I assign an onclick event to the delete button which assigns the deleteRecord function when the delete button is clicked. My issue is with the deleteRecord function. I have used this to refer to the calling element (the delete button) and wish to remove the task div that is the outermost container however I current get a message that says: 'Cannot read property 'parentNode' of undefined ' which suggests to me the this keyword is not working correctly.
Any help would be greatly appreciated.
I've added the full code to a fiddle.
http://jsfiddle.net/jezzipin/Bd8AR/
J
You need to provide the element itself as a parameter. I did so by changing the html to include onclick="deleteRecord(this)" to make it a little easier to deal with. This means you can remove the assignDeleteOnclick() function
function deleteRecord(elem){
elem.parentNode.parentNode.remove();
}
Demo
You might style the .content to be hidden better if there are no elements to prevent that extra white space
Edit
Since you don't want an inline onclick, you can do it with js the same:
function deleteRecord(elem){
elem.parentNode.parentNode.remove();
}
function assignDeleteOnclick(){
var deleteArray = document.getElementsByClassName('delete');
for(var i=0;i<deleteArray.length;i++){
// Has to be enveloped in a function() { } or else context is lost
deleteArray[i].onclick=function() { deleteRecord(this); }
}
}
Demo

Javascript Weird - I'm clueless

I'm working on this menu-system that's very similar to how operating systems do them.
Using jquery etc.
I have 2 comments down in the For Loop. It's basically outputting the last index each in the $(document).on('click')... function. But outside the document.on it works fine.
It's probably just an obvious problem but I've spent about an hour on this.. Thanks in advance!
menu: function(title) {
this.title = title;
this.slug = slugify(title);
this.icon = false;
this.buttons = Object();
this.num_buttons = 0;
this.visible = false;
this.timeout_id = null;
this.is_hovering_dropdown = false;
this.is_hovering_menu = false;
this.render = function() {
var that = this;
var slug = that.slug;
var str = '<li id="menu-' +slug +'">' + this.title + '';
if (this.num_buttons > 0) {
str += '<ul id="menu-dropdown-' + slug + '" style="display: none;" class="dropdown">';
for (var button in this.buttons) {
str += '<li>' +that.buttons[button]['title'] +'</li>'
alert(button) //new project, open project, save as etc.
$(document).on("click", "#menu-dropdown-" +slug + '-' + that.buttons[button]['slug'], function() {
$("#menu-dropdown-" + slug).hide("fade", 200);
that.visible = false;
alert(button);//save as, save as, save as, save as etc.
});
}
}
}
}
Here you go:
Thanks to the order of operations, and scoping, all of your buttons are being saved with a reference to the LAST value of button.
What you want to do is put that assignment inside of an immediately-invoking function, and pass the button into that particular function-scope.
(function (button) { $(document). //...... }(button));
Everything inside of the immediate function should still have access to the static stuff outside of the immediate-function's scope (ie: that), AND it will also have a reference to the current value of button, as it's being invoked then and there.
The longer version of the story is that your buttons, when being created are being given a reference to button, rather than the value of button, therefore, when they're actually invoked at a later time, they reference the value of button as it currently exists (ie: the last value it was assigned in the loop).

Categories