Javascript oop undefined reading length in loop - javascript

Im creating an oop project with multiple functions in a class. I have a piece of code, but since I'm using bind it doesn't work anymore.
document.getElementById("finalLink").innerHTML +=
"<a id='FLink' href='https://www.voetbalshop.nl/voetbalschoenen.html#' onclick='location.href=this.href+getLink(url);return false;'>Result</a>";
class QuestionControl{
/**
* #param {QuizPart[]} quiz
*/
constructor(quiz) {
this.quiz = quiz;
this.url = [];
this.questionNumber = -1;
this.button = document.getElementById('answer');
this.questionName = document.getElementById('question');
this.nextbtn = document.getElementById('nextbtn');
this.prevbtn = document.getElementById('prevbtn')
this.resultbtn = document.getElementById('FLink');
}
Initialize(){
this.NextQuestion();
}
/**
*
* #param {int} question
* #returns {QuizPart}
*/
SetQuestion(question){
if (this.questionNumber >= 0){
let oldAnswerButton = document.querySelectorAll('.filter_anwser');
// Deletes old question when the next question is clicked
for (let answerButton of oldAnswerButton) {
answerButton.style.display = 'none';
}
}
this.questionNumber = question;
let q = this.quiz[question];
// Check if your at the last question so the next button will stop being displayed.
if (this.questionNumber === Quiz.length-1) {
this.nextbtn.style.display = 'none';
this.prevbtn.style.display = 'block';
this.resultbtn.style.display = 'grid';
} else if (this.questionNumber === 0 ) {
this.nextbtn.style.display = 'block';
this.prevbtn.style.display = 'none';
this.resultbtn.style.display = 'none';
} else{
this.nextbtn.style.display = 'block';
this.prevbtn.style.display = 'block';
this.resultbtn.style.display = 'none';
}
// Displays Question
this.questionName.textContent = q.questionDescription;
this.questionName.id = "questionID";
return q;
}
NextQuestion() {
let question = this.SetQuestion.bind(this.questionNumber + 1);
// Displays answers of the questions
for (let y = 0; y < question.chosenAnswer.length; y++) {
let item = question.chosenAnswer[y];
// Display answer buttons
let btn = document.createElement('button');
btn.value = item.id;
btn.className = "filter_anwser";
btn.textContent = item.name;
this.button.appendChild(btn);
}
}
PrevQuestion() {
let question = this.SetQuestion(this.questionNumber - 1);
// Displays answers of the questions
for (let y = 0; y < question.chosenAnswer.length; y++) {
let item = question.chosenAnswer[y];
// Display answer buttons
let btn = document.querySelector('button[value="' + item.id + '"]');
btn.style.display = 'block';
console.log(btn);
}
}
/**
* Returns the parameters for the URL.
*
* #returns {string}
*/
getLink() {
let tmp = [];
for (let i = 0; i < this.url.length; i++) {
// Check if question is from the same quiz part and adds a , between chosen answers and add the right prefix at the beginning
if (this.url[i].length > 0){
tmp.push("" + Quiz[i].prefix + this.url[i].join(","))
}
}
/// If answers are from different quiz parts add a & between answers.
console.log(this.url, this.questionNumber);
return "" + tmp.join("&");
};
}
class QuizPart{
constructor(questionDescription, chosenAnswer, prefix){
this.questionDescription = questionDescription;
this.chosenAnswer = chosenAnswer;
this.prefix = prefix;
}
}
class ChosenAnswer{
constructor(id, name){
this.id = id;
this.name = name;
}
}
let Quiz = [
new QuizPart('Whats your size?', [
new ChosenAnswer('6595', '41'),
new ChosenAnswer('6598', '42'),
new ChosenAnswer('6601', '43'),
], 'bd_shoe_size_ids='),
new QuizPart('What color would you like?', [
new ChosenAnswer('6053', 'Red'),
new ChosenAnswer('6044', 'Blue'),
new ChosenAnswer('6056', 'Yellow'),
new ChosenAnswer('6048', 'Green'),
], 'color_ids='),
new QuizPart('What brand would you like?', [
new ChosenAnswer('5805', 'Adidas'),
new ChosenAnswer('5866', 'Nike'),
new ChosenAnswer('5875', 'Puma'),
], 'manufacturer_ids='),
]
let control = new QuestionControl(Quiz);
control.Initialize();
document.getElementById('nextbtn').addEventListener("click", control.NextQuestion);
document.getElementById('prevbtn').addEventListener("click", control.PrevQuestion);
control.button.addEventListener("click", function (e) {
const tgt = e.target;
// clear the url array if there's nothing clicked
if (control.url.length === control.questionNumber) {
control.url.push([]);
}
let quizUrl = control.url[control.questionNumber];
// Check if a button is clicked. Changes color and adds value to the url array.
if (quizUrl.indexOf(tgt.value) === -1) {
quizUrl.push(tgt.value);
e.target.style.backgroundColor = "orange";
// Check if a button is clicked again. If clicked again changes color back and deletes value in the url array.
} else {
quizUrl.splice(quizUrl.indexOf(tgt.value), 1);
e.target.style.backgroundColor = "white";
}
console.log(control.getLink());
})
The problem is that when i started using bind to connect the function SetQuestion to NextQuestion, it gave me the following error:
Uncaught TypeError: Cannot read properties of undefined (reading 'length')
At the for loop in nextQuestion, but if I stop using bind it won't recognize the function SetQuestion. Does anybody know why this happens?

This is not the correct use of bind:
let question = this.SetQuestion.bind(this.questionNumber + 1);
bind returns a function; it doesn't execute it. The argument you pass to bind should be a value to be used for this when eventually that returned function is called.
As this is not the intention here, you should remove .bind here, and just have:
let question = this.SetQuestion(this.questionNumber + 1);
The problem really originates here:
document.getElementById('nextbtn').addEventListener("click", control.NextQuestion);
document.getElementById('prevbtn').addEventListener("click", control.PrevQuestion);
These click handler functions will be called without a specific this value (it will be undefined in strict mode, or else the global object). This is not what you want, since your code expects this to represent an instance of QuestionControl.
This can be solved with bind as follows:
document.getElementById('nextbtn').addEventListener("click", control.NextQuestion.bind(control));
document.getElementById('prevbtn').addEventListener("click", control.PrevQuestion.bind(control));
So it is not as you write:
using bind to connect the function SetQuestion to NextQuestion
bind is used to connect a function to the object that must act as the this value during the execution of the function. In your case that is control.
Alternatively the binding function can be provided explicitly:
document.getElementById('nextbtn').addEventListener("click", () => control.NextQuestion());
document.getElementById('prevbtn').addEventListener("click", () => control.PrevQuestion());

Related

Javascript chaining window.onload functions

I have a javascript function that grabs a dataset numeric value and appends it to an XMLhttprequest parameter. It has to run onload as the content is printed dynamically through php.
I now am trying to create a simple carousel for the elements printed and finding some difficulty chaining onload functions.
I've found creating an additional onload function for anything breaks the first onload function. What can I do here?
function userData() {
let infoWrap = document.querySelector(".agent-detail-info").dataset.id;
console.log(infoWrap);
return infoWrap;
}
window.onload = userData;
window.onload = () => {
const request = new XMLHttpRequest();
request.open(
"GET",
`url&usertolookup=${userData()}`
);
request.onload = function () {
let response = request.response;
let parsedData = JSON.parse(response);
console.log(parsedData);
let testimonials = parsedData.data.testimonials.details;
testimonials.forEach((testimonial, index) => {
const testimonialWrap = document.querySelector(".testimonials");
// Create testimonials container
let innerTestimonials = document.createElement("div");
innerTestimonials.className = "inner-testimonial-container";
testimonialWrap.appendChild(innerTestimonials);
// Create star rating container
let starWrap = document.createElement("div");
starWrap.className = "testimonial-stars";
innerTestimonials.appendChild(starWrap);
// Create Testimonial Content
let innerTestimonialParagraph = document.createElement("p");
innerTestimonials.appendChild(innerTestimonialParagraph);
// Create Testimonial Signature
let innerTestimonialSignature = document.createElement("address");
innerTestimonials.appendChild(innerTestimonialSignature);
// Loop through rating value and create elements
let rating = testimonial.rating;
for (let i = 0; i < rating; i++) {
let star = document.createElement("i");
star.className = "fa fa-star";
starWrap.appendChild(star);
}
// Insert Testimonial Content
let testimonialText = testimonial.Testimonial;
innerTestimonialParagraph.innerHTML = testimonialText;
// Insert Testimonial Signature
let signature = testimonial.Signature;
innerTestimonialSignature.innerHTML = signature;
});
};
request.send();
};
Testing Carousel (have tried alternative with event listeners rather than inline onclick and cannot access the elements printed through the response(returns undefined as the elements are printed after dom load))
let tabIndex = 1;
function nextTestimonial(n) {
testimonialSlide((tabIndex += n));
}
function currentTestimonial(n) {
testimonialSlide((tabIndex = n));
}
function testimonialSlide(n) {
let innerTestimonials = document.querySelectorAll(
".inner-testimonial-container"
);
if (n > innerTestimonials.length) {
tabIndex = 1;
}
if (n < 1) {
tabIndex = innerTestimonials.length;
}
for (let i = 0; i < innerTestimonials.length; i++) {
innerTestimonials[i].style.display = "none";
}
innerTestimonials[tabIndex - 1].style.display = "block";
}
Random attempt to chain onload functions (this breaks the response)
window.onload = () => {
const innerTestimonialsNew = document.querySelectorAll(
".inner-testimonial-container"
);
console.log(innerTestimonialsNew);
};

Variable value not being saved in function

I want my filters variable to update, my guess is it's re-initializing as the set value every time the function is called, whenever i try to declare it outside of the function I get a lexical error, how can I make sure it keeps the value assigned to it after a button has clicked
export function categoryRender(){
let filter = 'RICK'
console.log(filter)
const all = document.getElementById('all');
all.onclick = function(){
filter = 'ALL'
render(filter);
}
categories = categories.sort();
const filterContainer = document.getElementById("filter-container");
filterContainer.innerHTML = "";
const allFilterImg = document.getElementById('all-image');
if (filter === 'ALL'){
allFilterImg.setAttribute('src', './images/checked.jpeg')
}else{
allFilterImg.setAttribute('src', './images/unchecked.png')
console.log('unchecked all firing')
}
for (let i = 0; i < categories.length; i++){
const line = document.createElement("span");
const filterButton = document.createElement("img");
const filterLabel = document.createElement("h2");
filterContainer.appendChild(line);
line.appendChild(filterButton);
line.appendChild(filterLabel);
line.setAttribute('id', categories[i]);
line.classList.add('filter-line');
filterLabel.innerHTML = categories[i];
if (filter === categories[i]){
filterButton.setAttribute('src', './images/checked.jpeg')
}else{
filterButton.setAttribute('src', './images/unchecked.png')
}
line.onclick = function(){
filter = categories[i];
render(filter)
}
}
}

use elements ID to find value in object JavaScript

I'm looping through some elements by class name, and adding event listeners to them. I then grab the id of the selected element (in this case "tom"), and want to use it to find the value of "role" in the "tom" object. I'm getting undefined? can anyone help?
var highlightArea = document.getElementsByClassName('highlightArea');
for (var i = 0; i < highlightArea.length; i++) {
highlightArea[i].addEventListener("mouseover", showPopup);
highlightArea[i].addEventListener("mouseover", hidePopup);
}
function showPopup(evt) {
var tom = { title:'tom', role:'full stack man' };
var id = this.id;
var role = id.role;
console.log(role)
}
You are not selecting the elements correctly, the class is hightlightArea and you are querying highlightArea (missing a 't'), so, no elements are found (you can easily discover that by debugging or using console.log(highlightArea) that is the variable that holds the elements found.
Just because the id of an element is the same name as a var, it doesn't mean that it have the properties or attributes of the variable... So when you get the Id, you need to check which one is and then get the variable that have the same name.
Also, you are adding the same listener two times mouseover that way, just the last would work, it means just hidePopup. I changed to mouseenter and mouseleave, this way will work correctly.
After that, you will be able to achieve your needs. Below is an working example.
var highlightArea = document.getElementsByClassName('hightlightArea');
var mypopup = document.getElementById("mypopup");
var tom = { title:'tom', role:'marketing'};
var jim = { title:'jim', role:'another role'};
for (var i = 0; i < highlightArea.length; i++) {
highlightArea[i].addEventListener("mouseenter", showPopup);
highlightArea[i].addEventListener("mouseleave", hidePopup);
}
function showPopup(evt) {
let ElemId = this.id;
let role;
let title;
if (ElemId == 'tom'){
role = tom.role;
title = tom.title;
}else if (ElemId == 'jim'){
role = jim.role;
title = jim.title;
}
let iconPos = this.getBoundingClientRect();
mypopup.innerHTML = role;
mypopup.style.left = (iconPos.right + 20) + "px";
mypopup.style.top = (window.scrollY + iconPos.top - 60) + "px";
mypopup.style.display = "block";
}
function hidePopup(evt) {
mypopup.style.display = "none";
}
<div class="hightlightArea" id="jim">Div Jim</div>
<div class="hightlightArea" id="tom">Div Tom</div>
<div id="mypopup"></div>
in your function 'showPopup' you have this:
var id = this.id
but this.id is not defined. You probably meant to write this:
var title = dom.title;

Javascript error: object is not a function in a video poker script

So I have been writing a script to play a video (or really text-based) poker game as an exercise in learning Javascript. I have everything working to play through an instance of the game once, but on trying to run it a second time, it develops an error: "Uncaught TypeError: object is not a function"
This error comes up when trying to create a new hand.
Here is the relevant code, I left a few functions out that don't seem to be causing any issues:
//object constructor for card
function card(suite, faceValue) {
this.suite = suite,
this.faceValue = faceValue
}
//object constructor for hand
function hand(cards, handName, score, docHandName) {
this.cards = cards,
this.handName = handName,
this.score = score,
this.docHandName = docHandName
}
var deck = new Array;
var buildDeck = function() {
for (var i = 0; i <= 52; i++) {
if (i < 13) {
deck[i] = new card("Spades", i + 2);
}
else if (i < 26) {
deck[i] = new card("Clubs", i - 11);
}
else if (i < 39) {
deck[i] = new card("Hearts", i - 24);
}
else if (i < 52) {
deck[i] = new card("Diamonds", i - 37);
}
}
}
//pulls a card from location in deck specified by randomSpot()
var pullCard = function(spot) {
var newCard = deck[spot];
deck.splice(spot, 1);
return newCard;
}
//takes away a card each time
//passes into pullCard(spot) as spot
var pullCount = 0;
var randomSpot = function() {
var x = Math.floor(Math.random() * (52 - pullCount));
pullCount++;
return x;
}
var dealFiveCards = function() {
var card1 = pullCard(randomSpot());
var card2 = pullCard(randomSpot());
var card3 = pullCard(randomSpot());
var card4 = pullCard(randomSpot());
var card5 = pullCard(randomSpot());
var fiveCards = [card1, card2, card3, card4, card5];
return fiveCards;
}
function createNewHand() {
newHand = new hand();
newHand.cards = dealFiveCards();
return newHand;
}
var playOneGame = function() {
buildDeck();
hand = createNewHand();
hand.cards.sort(compare);
assignHandScore();
wager = prompt("How much do you bet?");
printHandValue();
dealAgain();
hand.cards.sort(compare);
assignHandScore();
payout = pays(wager);
printHandValue();
printPayout();
}
playAgain = "Y";
while (playAgain === "Y") {
playOneGame();
playAgain = prompt("Would you like to play again? Y/N").toUpperCase();
}
So the error occurs when trying to run the playOneGame() function a second time. The first time runs fine and a hand is created. The second time when it gets to hand = createNewHand(); it gives the object is not a function error.
To be clear, I have the hand created as an object, which contains properties cards, handName, score, docHandName where cards is an array of card objects, themselves containing properties of suite, faceValue.
The error gives the line newHand = new hand(); in function createNewHand() as the reference line.
Help?
The second line of playOneGame is overriding your global hand function with an instance of hand. So when createNewHand runs again hand it is no longer the same thing.
You should probably rename the function hand to Hand.

How to change a button from another function?

var ButtonFarmAtivada = new Array();
function X() {
var tableCol = dom.cn("td"); //cell 0
//create start checkbox button
ButtonFarmAtivada[index] = createInputButton("checkbox", index);
ButtonFarmAtivada[index].name = "buttonFarmAtivada_"+index;
ButtonFarmAtivada[index].checked = GM_getValue("farmAtivada_"+index, true);
FM_log(3,"checkboxFarm "+(index)+" = "+GM_getValue("farmAtivada_"+index));
ButtonFarmAtivada[index].addEventListener("click", function() {
rp_farmAtivada(index);
}, false);
tableCol.appendChild(ButtonFarmAtivada[i]);
tableRow.appendChild(tableCol); // add the cell
}
1) is it possible to create the button inside an array as I'm trying to do in that example? like an array of buttons?
2) I ask that because I will have to change this button later from another function, and I'm trying to do that like this (not working):
function rp_marcadesmarcaFarm(valor) {
var vListID = getAllVillageId().toString();
FM_log(4,"MarcaDesmarcaFarm + vListID="+vListID);
var attackList = vListID.split(",");
for (i = 0; i <= attackList.length; i++) {
FM_log(3, "Marca/desmarca = "+i+" "+buttonFarmAtivada[i].Checked);
ButtonFarmAtivada[i].Checked = valor;
};
};
For number 1) yes, you can.
function createInputButton(type, index) { // um, why the 'index' param?
// also, why is this function called 'createInputButton'
// if sometimes it returns a checkbox as opposed to a button?
var inputButton = document.createElement("input");
inputButton.type = type; // alternately you could use setAttribute like so:
// inputButton.setAttribute("type", type);
// it would be more XHTML-ish, ♪ if that's what you're into ♫
return inputButton;
}
I don't really understand part 2, sorry.

Categories