Can not call method of undefined - javascript

I'm trying to develop a javascript object that creates a menu in html.
The function receives an object as an argument. Among the object elements is a function that should be executed in an event handler called from a method of my object.
Here is my code :
Menu = function(config) {
var j = 0;
this.config = config;
this.make = function() {
for (i = 0; i < this.config.items.length; i++) {
var vid = document.createElement("div");
vid.className = this.config.cls;
vid.id += i;
document.body.appendChild(vid);
var txt = document.createTextNode(this.config.items[i]);
var pp = document.createElement("p");
pp.appendChild(txt);
vid.appendChild(pp);
}
document.addEventListener("keydown", this.scrolldown, false);
document.onkeydown = function(e) {
var keyCode = e.keyCode;
alert("functional");
if (keyCode == 40) {
alert("You hit key down");
var et = document.getElementById(j);
this.config.trait1(et);
j = j + 1;
} else {
alert("no");
}
}
};
return this;
};
when I call the function make after instantiating the object I have my elements created but my event isn't handled because of :
Uncaught TypeError: Cannot call method 'trait1' of undefined .
Can anyone help me? I saw many answers of the same question but none of the suggested solutions worked.

this inside the Menu function is not the same as this inside the onkeydown function.
Store the value of this in another variable and use that.
Menu = function () {
var myMenu = this; // I'm assuming that you will be calling `new Menu()`
document.onkeydown = function () {
myMenu.config.etc.etc.etc
}
}

Related

Access array from addEventListener

I have added a addEventListener to button click event. After clicking button I want to delete global array data.
Now i am not able to pass the array with this.arrayname, I'm trying below code. It's deleting the data but not updating the parent array .
Please let me know how to pass parameter to addEventListener or how to call function from addEventListener, so that I can update the array .
this.array = deletejson;
btn.addEventListener('click', function(event) { for (var i = 0; i < deletejson.length; i++) {
var cur = deletejson[i];
if (cur.keywordname == (document.getElementById("keywordname") as HTMLTextAreaElement).value) {
deletejson.splice(i, 1);
break;
}}});
complete code:
export class ExtractionConfigurationComponent implements OnInit {
array = [];
constructor(private adminWorkflowService:AdminWorkflowService) { }
ngOnInit() {}
addedkeywords(data: any){
btn.addEventListener('click', (event) => {
//var x = document.getElementById(this.id);
//x.style.display='none';
for (var i = 0; i < this.array.length; i++) {
var cur = this.array[i];
if (cur.keywordname == (document.getElementById("keywordname") as HTMLTextAreaElement).value) {
this.array.splice(i, 1);
break;
console.log("after deleting data" +this.array);
console.log("after deleting data" +JSON.stringify(this.array));}}});}
Assuming that the array is stored as this.array (it's unclear in what context you are doing this), then you need access to the lexical this in your event listener's callback. This can be done by using arrow functions:
btn.addEventListener('click', (event) => {
for (var i = 0; i < this.array.length; i++) {
var cur = this.array[i];
if (cur.keywordname == (document.getElementById("keywordname") as HTMLTextAreaElement).value) {
this.array.splice(i, 1);
break;
}
}
});

Adding "this" to event listener?

I was creating a quiz application and decided to switch from .onclick() to .addEventListener(). In order to get that to work I had to add event handlers.
The only way I got the listeners to work was by adding the following code to the Quiz object constructor..
document.getElementById('guess0').addEventListener('click', this);
document.getElementById('guess1').addEventListener('click', this);
This works but I am not sure why. What exactly is the "this" doing in place as a function?
Entire page of code for reference:
function Quiz(questions) {
this.questions = questions;
this.score = 0;
this.currentQuestionIndex = -1;
document.getElementById('guess0').addEventListener('click', this);
document.getElementById('guess1').addEventListener('click', this);
this.displayNext();
}
Quiz.prototype.displayNext = function(){
this.currentQuestionIndex++;
if(this.hasEnded()){
this.displayScore();
this.displayProgress();
}else{
this.displayCurrentQuestion();
this.displayCurrentChoices();
this.displayProgress();
}
};
Quiz.prototype.hasEnded = function() {
return this.currentQuestionIndex >= this.questions.length;
};
Quiz.prototype.displayScore = function() {
let gameOverHtml = "<h1>Game is over!</h1>";
gameOverHtml += "<h2>Your score was: " + this.score + "!</h2>";
let quizDiv = document.getElementById('quizDiv');
quizDiv.innerHTML = gameOverHtml;
};
Quiz.prototype.getCurrentQuestion = function() {
return this.questions[this.currentQuestionIndex];
};
Quiz.prototype.displayCurrentQuestion = function() {
let currentQuestion = document.getElementById('question');
currentQuestion.textContent = this.questions[this.currentQuestionIndex].text;
};
Quiz.prototype.displayCurrentChoices = function() {
let choices = this.getCurrentQuestion().choices;
for (let i = 0; i < choices.length; i++) {
let choiceHTML = document.getElementById('choice' + i);
choiceHTML.innerHTML = choices[i];
}
};
Quiz.prototype.handleEvent = function(event){
if(event.type === 'click'){
this.handleClick(event);
}
};
Quiz.prototype.handleClick = function(event){
event.preventDefault();
let choices = this.getCurrentQuestion().choices;
if(event.target.id === "guess0"){
this.guess(choices[0]);
} else if(event.target.id === "guess1"){
this.guess(choices[1]);
}
this.displayNext();
};
Quiz.prototype.displayProgress = function() {
let footer = document.getElementById('quizFooter');
if (this.hasEnded()) {
footer.innerHTML = "You have completed the quiz!";
} else {
footer.innerHTML = "Question " + (this.currentQuestionIndex + 1) + " of " + this.questions.length;
}
};
Quiz.prototype.guess = function(choice) {
if (this.getCurrentQuestion().checkAnswer(choice)) {
this.score++;
}
};
You are making Quiz a "class" (as we normally think about classes, even if JS doesn't really have them). When you do quiz = new Quiz(questions), inside the Quiz constructor, this refers to the newly created Quiz object. addEventListener can accept one of two different values for the listener parameter:
This must be an object implementing the EventListener interface, or a JavaScript function.
Your Quiz implements the requisite interface by implementing handleEvent function. Thus, when you pass your newly-created quiz (as this) to addEventListener, you will get quiz.handleEvent invoked when the event happens.

Value being undefined? [duplicate]

I'm appending onclick events to elements that I'm creating dynamically. I'm using the code below, this is the important part only.
Test.prototype.Show= function (contents) {
for (i = 0; i <= contents.length - 1; i++) {
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = function () { return that.ClickContent.apply(that, [contents[i]]); };
}
}
First it says that it's undefined. Then I changed and added:
var content = content[i];
menulink.onclick = function () { return that.ClickContent.apply(that, [content]); };
What is happening now is that it always append the last element to all onclick events( aka elements). What I'm doing wrong here?
It's a classical problem. When the callback is called, the loop is finished so the value of i is content.length.
Use this for example :
Test.prototype.Show= function (contents) {
for (var i = 0; i < contents.length; i++) { // no need to have <= and -1
(function(i){ // creates a new variable i
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = function () { return that.ClickContent.apply(that, [contents[i]]); };
})(i);
}
}
This immediately called function creates a scope for a new variable i, whose value is thus protected.
Better still, separate the code making the handler into a function, both for clarity and to avoid creating and throwing away builder functions unnecessarily:
Test.prototype.Show = function (contents) {
for (var i = 0; i <= contents.length - 1; i++) {
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = makeHandler(i);
}
function makeHandler(index) {
return function () {
return that.ClickContent.apply(that, [contents[index]]);
};
}
};
A way to avoid this problem altogether, if you don't need compatibility with IE8, is to introduce a scope with forEach, instead of using a for loop:
Test.prototype.Show = function (contents) {
contents.forEach(function(content) {
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = function() {
return that.ClickContent.call(that, content);
};
});
}

When I make a clone of the object shows me an error message

First the following is the code of my own javascript library.
(function() {
var lib = {
elems: [],
getElem: function() {
var tmpElem = [];
for (var i = 0; i < arguments.length; i++)
tmpElem.push(document.getElementById(arguments[i]));
this.elems = tmpElem;
tmpElem = null;
return this;
},
html: function(txt) {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].innerHTML = txt;
return this;
},
style: function(prob, val) {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].style[prob] = val;
return this;
},
addEvent: function(event, callback) {
if (this.elems[0].addEventListener) {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].addEventListener(event, callback, false);
} else if (this.elems[0].attachEvent) {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].attachEvent('on' + event, callback);
}
return this;
},
toggle: function() {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].style.display = (this.elems[i].style.display === 'none' || '') ? 'block' : 'none';
return this;
},
domLoad: function(callback) {
var isLoaded = false;
var checkLoaded = setInterval(function() {
if (document.body && document.getElementById)
isLoaded = true;
}, 10);
var Loaded = setInterval(function() {
if (isLoaded) {
clearInterval(checkLoaded);
clearInterval(Loaded);
callback();
}
}, 10);
}
};
var fn = lib.getElem;
for(var i in lib)
fn[i] = lib[i];
window.lib = window.$ = fn;
})();
Previously, I have used this way to use my own library, and works fine .
$.getElem('box').html('Welcome to my computer.');
But when updated the code of my own library, and I added
var fn = lib.getElem;
for(var i in lib)
fn[i] = lib[i];
To be using the element selector like this way
$('box').html('Welcome to my computer.');
But the problem began appear when added the updated code to clone the lib object TypeError: $(...).html is not a function.
And now I want to use the element selector like that
$('box').html('Welcome to my computer.');
instead of
$.getElem('box').html('Welcome to my computer.');
You create a variable fn which has a reference to "getElem" but since fn is not a property on your lib object then it means that when getElem refers to "this" it will be you global object which is propbably window.
Remove all the following 3 lines
var fn = lib.getElem;
for(var i in lib)
fn[i] = lib[i];
and then do this
window.$ = function () { return lib.getElem.apply(lib, arguments); };
This will allow getElem to be called as $ but maintaining "lib" as context.
Although I don't know exactly what you are trying to achieve with those additional lines, just by reading the code, lib.getElem does not have a function called html
lib does.
Hence, just var fn = lib; should do just fine.
There more ways to achieve this but the root cause is in your getElem() function: return this;
$ is a reference to that function. If you call $() it is called as a function and not as a method. Therefore this refers to window and window has, of course, no html() function.
You could do return lib; to fix the problem.

Javascript dynamic onChange event on select

I have this javascript snippet:
var selectName["id1","id2","id3"];
setOnClickSelect = function (prefix, selectName) {
for(var i=0; i<selectName.length; i++) {
var selId = selectName[i];
alert(selId);
$(selId).onchange = function() {
$(selId).value = $(selId).options[$(selId).selectedIndex].text;
}
}
}
But when I change value to my id1 element, the alert wrote me always "id3".
Can I fix it?
EDIT:
I've changed my snippet with these statements:
setOnChangeSelect = function (prefix, selectName) {
for(var i=0; i<selectName.length; i++) {
var selId = selectName[i];
$(selId).onchange = (function (thisId) {
return function() {
$(selId).value = $(thisId).options[$(thisId).selectedIndex].text;
}
})(selId);
}
}
But selId is always the last element.
This is caused by the behavior of javaScript Closure, selId has been set to the selectName[2] at the end of the loop and that's why you get 'id3' back.
An fix is as following, the key is wrap the callback function inside another function to create another closure.
var selectName = ["id1","id2","id3"];
var setOnClickSelect = function (prefix, selectName) {
for(var i = 0; i < selectName.length; i++) {
var selId = selectName[i];
$(selId).onchange = (function (thisId) {
return function() {
$(thisId).value = $(thisId).options[$(thisId).selectedIndex].text;
}
})(selId);
}
};
Ps: there is synyax error for var selectName["id1","id2","id3"], you should use var selectName = ["id1","id2","id3"];

Categories