Javascript undefined error: `this` is null - javascript

Somehow when executing this code, I get the alert from line 29 .mouseOnSeat.
But I don't know why this.seats is null, while in the draw function it is not.
I call the init function from html5.
//init called by html5
function init() {
var cinema = new Cinema(8, 10);
cinema.draw("simpleCanvas");
var canvas = document.getElementById("simpleCanvas");
//add event listener and call mouseOnSeat
canvas.addEventListener('mousedown', cinema.mouseOnSeat, false);
}
var Cinema = (function () {
function Cinema(rows, seatsPerRow) {
this.seats = [];
this.rows = rows;
this.seatsPerRow = seatsPerRow;
var seatSize = 20;
var seatSpacing = 3;
var rowSpacing = 5;
var i;
var j;
for (i = 0; i < rows; i++) {
for (j = 0; j < seatsPerRow; j++) {
this.seats[(i * seatsPerRow) + j] = new Seat(i, j, new Rect(j * (seatSize + seatSpacing), i * (seatSize + rowSpacing), seatSize, seatSize));
}
}
}
Cinema.prototype.mouseOnSeat = function (event) {
//somehow this is null
if (this.seats == null) {
alert("seats was null");
return;
}
for (var i = 0; i < this.seats.length; i++) {
var s = this.seats[i];
if (s.mouseOnSeat(event)) {
alert("Mouse on a seat");
}
}
alert("Mouse not on any seat");
};
Cinema.prototype.draw = function (canvasId) {
var canvas = document.getElementById(canvasId);
var context = canvas.getContext('2d');
var i;
//somehow this isn't
for (i = 0; i < this.seats.length; i++) {
var s = this.seats[i];
context.beginPath();
var rect = context.rect(s.rect.x, s.rect.y, s.rect.width, s.rect.height);
context.fillStyle = 'green';
context.fill();
}
};
return Cinema;
})();
I tried a lot, like creating a self variable (var self = this ) and then calling from self.mouseOnSeat, it was suggested on another post, but I didn't figure it out.

The problem is that when you call addEventListener, the variable this does not carry along to the function call. This means that this is not your object.
You workaround is sound, you can use it. Or alteratively change your addEventListener call to:
canvas.addEventListener('mousedown', cinema.mouseOnSeat.bind(this), false);
Do note that you might need to use a polyfill to get Function.prototype.bind for older browsers, although it is very well supported currently. See caniuse.

I found a workaround :
canvas.addEventListener('mousedown', function (event) {
cinema.mouseOnSeat(event);
}, false);
But I have no clue why

Related

Trigger an event by the execution of another one

I want to integrate the last two loops into the first two loops in order to trigger the events from the last ones when the first ones fire.
function expand() {
var coll = document.querySelectorAll(".grid-item");
for (x = 0; x < coll.length; x++) {
coll[x].addEventListener("mouseenter",
function () {
event.target.style.width = "480px";
});
}
for (x = 0; x < coll.length; x++) {
coll[x].addEventListener("mouseleave",
function () {
event.target.style.width = null;
});
}
var coll1 = document.querySelectorAll(".collapsing");
for (x = 0; x < coll1.length; x++) {
coll1[x].addEventListener("mouseenter",
function () {
event.target.style.maxHeight = "480px";
});
}
for (x = 0; x < coll1.length; x++) {
coll1[x].addEventListener("mouseleave",
function () {
event.target.style.maxHeight = null;
});
}
Maybe this helps you more than me as I am a js beginner...
With your for loop, you have the index of the .grid-items - simply select the item in the .collapsing nodeList at the same index, and then you can set its style as well:
const gridItems = document.querySelectorAll(".grid-item");
const collapsings = document.querySelectorAll(".collapsing");
for (let i = 0; i < gridItems.length; i++) {
gridItems[i].addEventListener("mouseenter", () => {
gridItems[i].style.width = "480px";
collapsings[i].style.maxHeight = "480px";
});
gridItems[i].addEventListener("mouseleave", () => {
gridItems[i].style.width = null;
collapsings[i].style.maxHeight = null;
});
}
(note the use of let i, that's very important - best to avoid implicitly creating global variables, and use const or let instead of var to avoid unintuitive behavior from hoisting/function scope)
Another option to consider, rather than changing styles manually like this, would be to toggle classes on and off instead. Also, if these elements are all in a container, you might think about using event delegation, that way you only ever add two events overall, rather than two events for every element.
Something like this?
function expand() {
var coll = document.querySelectorAll(".grid-item");
for (x = 0; x < coll.length; x++) {
coll[x].addEventListener("mouseenter",
function() {
event.target.style.width = "480px";
loop3(true);
});
}
for (x = 0; x < coll.length; x++) {
coll[x].addEventListener("mouseleave",
function() {
event.target.style.width = null;
loop4(true);
});
}
loop3(false);
function loop3(auto_trigger) {
var coll1 = document.querySelectorAll(".collapsing");
for (x = 0; x < coll1.length; x++) {
coll1[x].addEventListener("mouseenter",
function() {
event.target.style.maxHeight = "480px";
});
if (auto_trigger) coll1[x].style.maxHeight = "480px";
}
}
loop4(false);
function loop4(auto_trigger) {
var coll1 = document.querySelectorAll(".collapsing");
for (x = 0; x < coll1.length; x++) {
coll1[x].addEventListener("mouseleave",
function() {
event.target.style.maxHeight = null;
});
if (auto_trigger) coll1[x].style.maxHeight = null;
}
}
}

Javascript Works on Desktop but only Partially Works on Mobile

The following is the full JavaScript code for a quiz. It works perfectly on desktop browsers. However, when I went to test it on my phone, it doesn't work.
Mobile loads the JSON quiz, but after all the answers are selected, it does not freeze all the selected answers and display the results. Instead, nothing happens, no result is shown/calculated, and the user can continue to select answers.
I'm not experienced with JavaScript on Mobile and don't know what is causing the problem.
I've deleted a few parts of the code that simply appended html in order to cut down on size.
You can view it here: plnkr.co/edit/tkCQVxoIq9oOiApeUY66?p=preview
// Adds the functionality to check if an element has a class
HTMLElement.prototype.hasClass = function (className) {
"use strict";
if (this.classList) {
return this.classList.contains(className);
}
return (-1 < this.className.indexOf(className));
};
// Adds the ability to remove classes from elements
HTMLElement.prototype.removeClass = function (className) {
"use strict";
if (this.classList) {
this.classList.remove(className);
}
return this;
};
var BF_QUIZ = {};
BF_QUIZ.quiz = function () {
"use strict";
// Sets variables
var highest_score, quiz_div, quiz_title, quiz_image, questions = [],
results = [], inputs = [], answers = [], userAnswers = [],
// Gets the Quiz "canvas"
getQuizCanvas = function getQuizCanvas() {
quiz_div = document.getElementById("bf-quiz");
},
// Parses the JSON data passed from the Loader
getJSONData = function getJSONData(json_data) {
//Main Quiz Title
quiz_title = json_data[0].quiz_title;
//Main Quiz Image
quiz_image = json_data[0].quiz_image;
//Populates questions arrary with questions from JSON file
for (var i = 0; i < json_data[0].quiz_questions.length; i++) {
questions.push(json_data[0].quiz_questions[i]);
}
//Populates results array with results from JSON file
for (var j = 0; j < json_data[0].quiz_results.length; j++) {
results.push(json_data[0].quiz_results[j]);
}
},
// Writes the Quiz into the document
writeQuiz = function writeQuiz() {
var newQuizWrapper, newTitle, newQuestionTextWrapper, newQuestionText,
newAnswerForm, newAnswer, newAnswerImage, newAnswerTextWrapper, newAnswerInput,
newAnswerText, newQuestion;
newQuizWrapper = document.createElement("div");
newQuizWrapper.className = "quiz-wrapper";
newTitle = document.createElement("h1");
newTitle.innerHTML = quiz_title;
newQuizWrapper.appendChild(newTitle);
for (var i = 0; i < questions.length; i++) {
newQuestionTextWrapper = document.createElement("div");
newQuestionTextWrapper.className = "quiz-question-text-wrapper";
newQuestionText = document.createElement("h2");
newQuestionText.innerHTML = questions[i].question.text;
newQuestionTextWrapper.appendChild(newQuestionText);
newAnswerForm = document.createElement("form");
for (var j = 0; j < questions[i].question.question_answers.length; j++) {
newAnswer = document.createElement("div");
newAnswer.className = "quiz-answer";
newAnswer.setAttribute("data-quizValue",
questions[i].question.question_answers[j].answer.value);
if (questions[i].question.question_answers[j].answer.image) {
newAnswerImage = document.createElement("img");
newAnswerImage.src = questions[i].question.question_answers[j].answer.image;
newAnswer.appendChild(newAnswerImage);
}
else{
//no image
}
newAnswerTextWrapper = document.createElement("div");
newAnswerTextWrapper.className = "quiz-answer-text-wrapper";
newAnswerTextWrapper.id = "quiz-answer-text-wrapper";
newAnswerInput = document.createElement("input");
newAnswerInput.type = "radio";
newAnswerInput.name = "answer";
inputs.push(newAnswerInput);
newAnswerText = document.createElement("label");
newAnswerText.htmlFor = "quizzer";
newAnswerText.innerHTML = questions[i].question.question_answers[j].answer.text;
newAnswerTextWrapper.appendChild(newAnswerInput);
newAnswerTextWrapper.appendChild(newAnswerText);
newAnswer.appendChild(newAnswerTextWrapper);
answers.push(newAnswer);
newAnswerForm.appendChild(newAnswer);
}
newQuestion = document.createElement("div");
newQuestion.className = "quiz-question";
newQuestion.appendChild(newQuestionTextWrapper);
newQuestion.appendChild(newAnswerForm);
newQuizWrapper.appendChild(newQuestion);
}
quiz_div.appendChild(newQuizWrapper);
},
//Checks all of the inputs to see if the
checkInputs = function checkInputs() {
var c = 0;
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].checked) {
userAnswers.push(inputs[i].parentNode.parentNode.dataset.quizvalue);
c++;
}
}
if (c==questions.length) {
calcResult();
}
},
calcResult = function calcResult() {
var highest = 0;
for (var i = 0; i < results.length; i++) {
results[i].countof = 0;
for (var j = 0; j < userAnswers.length; j++) {
if (userAnswers[j] == results[i].result.id) {
results[i].countof++;
}
}
if (results[i].countof > highest) {
highest = results[i].countof;
highest_score = results[i];
}
}
//disable the inputs after the quiz is finished
writeResult();
disableAnswers();
},
writeResult = function writeResult() {
newResult = document.createElement("div");
//append html to render (quiz result)
...;
quiz_div.appendChild(newResult);
},
updateSelectedAnswer = function updateSelectedAnswer(element) {
element.children.namedItem("quiz-answer-text-wrapper").firstChild.checked = true;
for (var i = 0; i < element.parentNode.children.length; i++) {
if (element.parentNode.children.item(i).hasClass("selected")) {
element.parentNode.children.item(i).removeClass("selected");
}
}
element.className = element.className + " selected";
},
addClickEvents = function addClickEvents() {
var onAnswerClick = function onAnswerClick() {
if (!this.hasAttribute("disabled")) {
updateSelectedAnswer(this);
checkInputs();
}
};
for (var i = 0; i < answers.length; i++) {
answers[i].addEventListener("click", onAnswerClick);
}
},
disableAnswers = function disableAnswers() {
for (var q = 0; q < answers.length; q++) {
answers[q].disabled = true;
answers[q].setAttribute("disabled", true);
answers[q].className = answers[q].className + " disabled";
}
};
return {
init: function (json_data) {
getQuizCanvas();
getJSONData(json_data);
writeQuiz();
addClickEvents();
}
};
}();
BF_QUIZ.quizLoader = function () {
"use strict";
var json_data, request,
loadQuizJSON = function loadQuizJSON(json_url) {
request = new XMLHttpRequest();
request.open("GET", json_url, false);
request.onload = function() {
if (request.status >= 200 && request.status < 400) {
// Success!
json_data = JSON.parse(request.responseText);
} else {
// We reached our target server, but it returned an error
}
};
request.onerror = function() {
// There was a connection error of some sort
};
request.send();
};
return {
init: function(json_url) {
loadQuizJSON(json_url);
BF_QUIZ.quiz.init(json_data);
}
};
}();
If you ever try to see console log on a mobile device, you might notice that there is a JavaScript error because iOS Safari is less forgiving than whatever desktop browser you use. Particularly it is illegal to set style property of HTMLElement as string in strict mode. You may see examples of the ways to set it properly at https://developer.mozilla.org/en/docs/Web/API/HTMLElement/style If you fix this issue, code seems to be working on Mobile Safari as well.
P.S. note that the offending code is missing in your question and is only visible in the full code of writeResult on plunker. This is why it is so important to provide a Minimal, Complete, and Verifiable example
From your plunker, if you update your code on line 152 from newResultTitle.style = "color:rgba(238,62,52,.99);"; to newResultTitle.style.color = "rgba(238,62,52,.99)"; this would make it work on mobile browsers.
HTMLElement.style reruns a read only property, desktop browser ignoring it when you are trying to assign a value to it, and mobile browser throwing an error.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style
Working plunker http://plnkr.co/edit/aVxTP5YCbL94v2GI0IKh?p=preview

Changing <this> in object literal

I'm creating an object literal and I want to use the reserved word "this". The problem I'm having is that the "this" points to the window object in an object literal. I know the this points to the current object when used in a constructor function. Is there a way to override it so that "this" points to my object literal?
main = {
run: function()
{
var elements = [];
var allElements = document.querySelectorAll("*");
for(var i = 0; i < allElements.length; i++)
{
if(allElements[i].nodeType != 3)
{
elements.push(allElements[i]);
}
}
for(var i = 0; i < elements.length; i++)
{
// Doesn't work
// this.parseElement(elements[i]);
// Works
main.parseElement(elements[i]);
}
},
parseElement: function(e)
{
// Unimportant code
}
}
(function()
{
main.run();
})();
The thing you claim works in your question doesn't work:
var main = {
run: (function()
{
var elements = [];
var allElements = document.querySelectorAll("*");
for(var i = 0; i < allElements.length; i++)
{
if(allElements[i].nodeType != 3)
{
elements.push(allElements[i]);
}
}
for(var i = 0; i < elements.length; i++)
{
// Doesn't work
// this.parseElement(elements[i]);
// Works
main.parseElement(elements[i]);
}
})(),
parseElement: function(e)
{
// Unimportant code
}
};
<div></div>
Fundamentally, you cannot refer to the object being constructed from within the object initializer. You have to create the object first, because during the processing of the initializer, while the object does exist no reference to it is available to your code yet.
From the name run, it seems like you want run to be a method, which it isn't in your code (you've edited the question now to make it one). Just remove the ()() around the function:
var main = {
run: function() {
var elements = [];
var allElements = document.querySelectorAll("*");
for (var i = 0; i < allElements.length; i++) {
if (allElements[i].nodeType != 3) {
elements.push(allElements[i]);
}
}
for (var i = 0; i < elements.length; i++) {
this.parseElement(elements[i]);
}
},
parseElement: function(e) {
console.log("Parsing " + e.tagName);
}
};
main.run();
<div></div>
Since this is set by how the function is called for normal functions, if you want run to be bound to main so that it doesn't matter how it's called, using main instead of this is the simplest way to do that in that code.
But if you don't want to use main, you could create a bound function:
var main = {
run: function() {
var elements = [];
var allElements = document.querySelectorAll("*");
for (var i = 0; i < allElements.length; i++) {
if (allElements[i].nodeType != 3) {
elements.push(allElements[i]);
}
}
for (var i = 0; i < elements.length; i++) {
this.parseElement(elements[i]);
}
},
parseElement: function(e) {
console.log("Parsing " + e.tagName);
}
};
// Bind run
main.run = main.run.bind(main);
// Use it such that `this` would have been wrong
// if we hadn't bound it:
var f = main.run;
f();
<div></div>
Just as a side note, we can use Array.prototype.filter and Array.prototype.forEach to make that code a bit more concise:
var main = {
run: function() {
var allElements = document.querySelectorAll("*");
var elements = Array.prototype.filter.call(allElements, function(e) {
return e.nodeType != 3;
});
elements.forEach(this.parseElement, this);
},
parseElement: function(e) {
console.log("Parsing " + e.tagName);
}
};
// Use it
main.run();
<div></div>
That assumes that parseElement only ever looks at the first argument it's given (since forEach will call it with three: the entry we're visiting, its index, and the object we're looping through).

banner ads not visible in html page

i have a file banners.js
function addEvent(object, evName, fnName, cap) {
if (object.attachEvent)
object.attachEvent("on" + evName, fnName);
else if (object.addEventListener)
object.addEventListener(evName, fnName, cap);
}
var nextAd;
function makeBannerAds() {
var bannerBox = document.createElement("div");
bannerBox.id = "bannerBox";
document.body.appendChild(bannerBox);
for (var i=0; i<adsURL.length; i++) {
var bannerAd = document.createElement("div");
bannerAd.className = "bannerAd";
bannerAd.style.zIndex = i;
var urlLink = document.createElement("a");
urlLink.href = adsURL[i];
var bannerIndex = document.createElement("img");
bannerIndex.src = "banner" + i +".jpg";
bannerIndex.style.width="290px";
bannerIndex.style.height="55px";
bannerBox.appendChild(bannerAd);
}
bannerBox.appendChild(bannerAd);
setInterval("changeBannerAd()", 10000);
}
function changeBannerAd() {
var allAds = document.getElementById("bannerBox").childNodes;
alert('work');
for(var i=0; i<num; i++) {
if(allAds.style.zIndex == 0) {
allAds.style.top = "-50px";
nextAd = allAds;
}
}
for(var i=0; i<num; i++) {
allAds.style.zIndex--;
if(allAds.style.zIndex < 0)
allAds.style.zIndex = num-1;
}
var timeDelay = 0;
for(var i=-50; i<=0; i++) {
setTimeout("moveNextAd(" + i + ")", timeDelay);
timeDelay += 15;
}
}
function moveNextAd(top) {
nextAd.style.top = top + ".px"
}
addEvent(window, "load", makeBannerAds(), false);
the second file
ads.js
var adsURL = new Array();
//this stores each item in the array using a index place holder
adsURL[0] = "testpage0.htm";
adsURL[1] = "testpage1.htm";
adsURL[2] = "testpage2.htm";
adsURL[3] = "testpage3.htm";
adsURL[4] = "testpage4.htm";
adsURL[5] = "testpage5.htm";
adsURL[6] = "testpage6.htm";
adsURL[7] = "testpage7.htm";
adsURL[8] = "testpage8.htm";
adsURL[9] = "testpage9.htm";
adsURL[10] = "testpage10.htm";
adsURL[11] = "testpage11.htm";
//and an html file where these are included.
javascript is not showing any error and also all the statements are running but the images are not visible on the page. i couldnot figure the problem. working from last 2 days.
New answer:
Some of your code had to be revised I think. I just edited this off of what I thought it was supposed to look like.
Check the new jsFiddle
I think it might be getting you closer.
Briefly, the issue was that childNodes() returns an array of elements, so you need to reference that variable as you would an array
Secondly, you didn't have all the appends that were required.
var adsURL = new Array();
adsURL[0] = "testpage0.htm";
...
adsURL[11] = "testpage11.htm";
var nextAd;
var moveNextAd = function (top) {
nextAd.style.top = top + ".px"
}; //changed this to function as variable, answer explained below
var changeBannerAd = function () {
var num = adsURL.length
var allAds = document.getElementById("bannerBox")
allAds = allAds.childNodes;
//^^^ here is what was wrong
// Child nodes returns an array of elements
// So for allAds, you should reference it with allAds[i]
for (var i = 0; i < num; i++) {
if (allAds[i].style.zIndex == 0) {
allAds[i].style.top = "-50px";
nextAd = allAds[i++];
}
}
for (var i = 0; i < num; i++) {
allAds[i].style.zIndex--;
if (allAds[i].style.zIndex < 0) allAds[i].style.zIndex = num - 1;
}
var timeDelay = 0;
for (var i = -50; i <= 0; i++) {
setTimeout((function () {
moveNextAd(" + i + ");
}()), timeDelay);
timeDelay += 15;
}
}; //changed this to function as variable too.
function addEvent(object, evName, fnName, cap) {
if (object.attachEvent) object.attachEvent("on" + evName, fnName);
else if (object.addEventListener) object.addEventListener(evName, fnName, cap);
}
function makeBannerAds() {
var bannerBox = document.createElement("div");
bannerBox.id = "bannerBox";
document.body.appendChild(bannerBox);
for (var i = 0; i < adsURL.length; i++) {
var bannerAd = document.createElement("div");
bannerAd.className = "bannerAd";
bannerAd.style.zIndex = i;
bannerAd.style.position = "absolute";
var urlLink = document.createElement("a");
urlLink.href = adsURL[i];
var bannerIndex = document.createElement("img");
bannerIndex.src = "banner" + i + ".jpg";
bannerIndex.style.width = "290px";
bannerIndex.style.height = "55px";
urlLink.appendChild(bannerIndex); //Here is the other problem
bannerAd.appendChild(urlLink); //All these weren't appended
bannerBox.appendChild(bannerAd); // to each other
}
setInterval((function () {
changeBannerAd();
}()), 10000);
}
addEvent(window, "load", makeBannerAds(), false);
Secondly, not to be picky or anything but you should probably declare the functions you pass into setInterval or setTimeout as variables rather than explicit functions or run them through an anonymous function like I have done. These two functions use a form of eval, and as we all know eval is evil, eval is evil, and eval is evil
Old answer; ignore
Question: Is your ads.js added before or after banners.js
Try adding the script tag before banners.js
I just placed the ads.js content above the banners.js script in a jsFiddle and it worked just fine for me
Check the JS Fiddle for full code at the top.

passing array to Constructor function and keep it public

here is my code :
var BoxUtility = function() {
var boxList = Array.prototype.pop.apply(arguments);
};
Object.defineProperties(BoxUtility, {
totalArea: {
value: function(){
var x = 0;
for(var i = 0, len = boxList.length; i <= len - 1; i++){
x = x + boxList[i].area;
};
return x;
}
}
});
I'm trying to achieve this syntax for my Code :
var boxArray = [box01, box02, box03];
box are objects, box01.area => boxes have area property
var newElement = new BoxUtility(boxArray);
alert(newElement.totalArea);
I WANT TO SEE THE RESULT AS I EXPECT but I think boxList is in another scope
How can I reach it in defineProperties
You have to assign the value to a property of this in your constructor.
var BoxUtility = function() {
// this.boxList
this.boxList = Array.prototype.pop.apply(arguments);
};
// instance methods go on the prototype of the constructor
Object.defineProperties(BoxUtility.prototype, {
totalArea: {
// use get, instead of value, to execute this function when
// we access the property.
get: function(){
var x = 0;
// this.boxList
for(var i = 0, len = this.boxList.length; i <= len - 1; i++){
x = x + this.boxList[i].area;
};
return x;
}
}
});
var boxUtil = new BoxUtility([{area:123}, {area:456}]);
console.log(boxUtil.totalArea); // 579
Variable scope is always at the function level. So you declared a local variable that is only usable inside your constructor function. But every time you call the constructor function you get a new object (this). You add properties to this in order to have those properties accessible in your instance methods on the prototype.
this works
var BoxUtility = function() {
this.boxList = Array.prototype.pop.apply(arguments);
Object.defineProperties(this, {
totalArea: {
get: function(){
var x = 0;
for(var i = 0, len = this.boxList.length; i <= len - 1; i++){
x = x + this.boxList[i].area;
};
return x;
}
}
});};
var y = new BoxUtility(boxArray);
alert(y.totalArea)
This is simple way to pass array as argument in constructer and declare function prototype for public access.
function BoxUtility(boxArray) {
this.boxArray = boxArray;
this.len = boxArray.length;
}
Color.prototype.getAverage = function () {
var sum = 0;
for(let i = 0;i<this.len;i++){
sum+=this.boxArray[i];
}
return parseInt(sum);
};
var red = new BoxUtility(boxArray);
alert(red.getAverage());

Categories

Resources