Trying to add an object key to button. "Cannot read undefined" - javascript

I'm creating this code to create a quiz.
The undefined error shows up when I'm trying to setData to give my buttons key strings. I'm doing so to try use that to check whether the answer buttons get assigned certain classes "correct" or "incorrect".
These buttons aren't fully fleshed out yet. Because I'm stuck on this one part.
But, I get an undefined error in the selectAnswer function loop. I'm stuck and don't know what I'm doing wrong. Any pointers would be nice.
// This document, when the start button is clicked, shows questions, & starts the timer
var startButton = document.getElementById("start-btn");
var questionContainerEl = document.getElementById("question-container");
var startScreen = document.getElementById("start-screen");
var questionTitle = document.getElementById("question");
var choiceButtons = document.getElementById("choice-buttons");
//var buttonVal = 0;
var score = 0;
var shuffleQuestions, currentQindex;
var i = 0;
//Object Container to hold questions and answers
const questions = [{
question: "What is 2 + 2?",
answers: [
{text: "4", correct: "true"},
{text: "2", correct: "false"},
{text: "3", correct: "false"},
{text: "5", correct: "false"}
]
},
{
question: "What is 4*4?",
answers: [{text: '8', correct: "false"},
{text: '16',correct: "false"},
{text: '2', correct: "false"},
{text: '6', correct: "false"},
]
},
{
question: "Solve for x. y = 3x - 6",
answers: [{text: '6', correct: "false"},
{text: '3', correct: "false"},
{text: '2', correct: "false"},
{text: "idk", correct: "false"}]
}];
startButton.addEventListener('click',startGame);
//Start game function shows Question Cont. Shuffles Questions on an Arr.
function startGame() {
startScreen.classList.add('hide');
shuffleQuestions = questions.sort(() => Math.random() - .5);
currentQindex = 0;
questionContainerEl.classList.remove('hide');
setNextQuestion();
}
//Erases question and finds next one on Arr.
function setNextQuestion() {
resetState();
showQuestion(shuffleQuestions[currentQindex])
}
//Shows question as well as answers.
function showQuestion(questions) {
questionTitle.innerText = questions.question;
console.log(questionTitle);
var ansLength = Object.keys(questions.answers).length;
var ans;
for(i = 0; i < ansLength; i++) {
//When the answer button is selected questions goes to next object element
ans = questions.answers[i];
console.log(ans);
var button = document.createElement("button");
button.innerText = ans.text;
button.classList.add('btn');
if(ans.correct === "true") {
button.correct = ans.correct;
console.log("button:" + button.correct);
}
button.addEventListener("click", selectAnswer);
choiceButtons.appendChild(button);
}
}
//removes buttons from choices.
function resetState() {
while(choiceButtons.firstChild) {
choiceButtons.removeChild(choiceButtons.firstChild)
}
}
function selectAnswer(e) {
var selectedButton = e.target;
var isCorrect = selectedButton.dataset.correct;
setStatusClass(document.body, isCorrect);
var ar = Array.from(choiceButtons.children)
for (buttons in ar) {
setStatusClass(buttons, buttons.dataset.isCorrect);
}
}
function setStatusClass(element, correct) {
clearStatusClass(element);
if (correct) {
element.classList.add("correct");
} else {
element.classList.add("incorrect");
}
}
function clearStatusClass(element){
element.classList.remove("correct");
element.classList.remove("incorrect");
}'''

Related

Combine the result of two input fields with JavaScript?

I have as a result from an input form a couple of strings and I want them to convert them, so they fit as data for my ajax-request. I am looking for an easy way, but I can't get it right. Basically I want to convert/map this array:
[
{ name: "[1][apples]", value: "2" }
{ name: "[1][melons]", value: "1" }
{ name: "[2][apples]", value: "2" }
{ name: "[2][melons]", value: "4" }
{ name: "[3][apples]", value: "3" }
{ name: "[3][melons]", value: "2" }
]
into
[{"id": 1, "apples": 2, "melons": 1}, {"id": 2, "apples": 2, "melons": 4}, {...}]
Any idea? I would appreciate some hint? I could't not find an easy solution via html though.
Thanks
you can use a for loop to access each element and display them.
Refer to this link. For loop in multidimensional javascript array
Firstly, I have replaced the square brackets using a regular expression and formed a new array. After that, I have merged object having same ID using spread operator.
You can refer to the code below which solves this problem.
let array = [
{ name: "[1][apples]", value: "2" },
{ name: "[1][melons]", value: "1" },
{ name: "[2][apples]", value: "2" },
{ name: "[2][melons]", value: "4" },
{ name: "[3][apples]", value: "3" },
{ name: "[3][melons]", value: "2" }];
let newArray = [];
let result = [];
array.forEach((obj, i) => {
let nameArray = obj.name.replace(/[\[\]']+/g, ' ').trim().split(' ');
let o = {};
o['id'] = parseInt(nameArray[0]);
o[nameArray[1]] = obj.value;
newArray.push(o);
});
for(let i = 0; i< newArray.length; i++) {
for(let j = i+1; j < newArray.length; j++) {
if(newArray[i].id === newArray[j].id) {
let o = {...newArray[i], ...newArray[j]};
result.push(o);`enter code here`
}
}
}
console.log('Final result', result);
Thanks for the input. I think my question needed to be more specific:
(1) Yes, they are always in order.
(2) My names of my input-tags in html appear to be an multidimensional array. This is not the case! I tried something, but it turned out to be for php.
I found the follow workaround:
function apiAdapter() {
var arrayToCopy = $("#formsteps").serializeArray();
var copiedArray = [];
for (let i = 0; i < arrayToCopy.length; i += 2) {
var id = arrayToCopy[i].name.slice(arrayToCopy[i].name.indexOf('[') + 1, arrayToCopy[i].name.indexOf(']'));
copiedArray.push({ "id": id, "apples": arrayToCopy[i].value, "melons": arrayToCopy[i + 1].value })
}
return copiedArray;
}
As I am new to JavaScript I always look for better solutions. So thanks again for your posts.

How to get inside this if statement (JavaScript)

I'm creating this simple quiz with 6 questions that I'm storing in an array. What I'm stuck with is that when you've reached the last question and you click 'Next', I'd like to show your results along with a text and a 'Restart' button.
What happens now though is that it show the last question again but without any answer-butons (I'm not getting into that if statement).
I'm pretty new to js, here's my code.
const startQuiz = document.querySelector('.container__start-btn');
const theQuestion = document.querySelector('.container__question');
const fourAnswers = document.querySelector('.container__answers');
const counter = document.querySelector('.container__counter__correct-answer');
const questionCounter = document.querySelector('.container__counter__question-nr');
const containerCounter = document.querySelector('.container__counter');
let currentQuestion = 0;
let count = 0;
const showQuestion = () => {
startQuiz.innerText = 'NEXT';
while (fourAnswers.firstChild) {
fourAnswers.removeChild(fourAnswers.firstChild);
}
theQuestion.innerText = questions[currentQuestion].question;
checkCounters();
}
const checkCounters = () => {
counter.textContent = `Score: ${count}`;
questionCounter.textContent = `Question: ${currentQuestion + 1}/${questions.length}`;
generateAnswers();
}
const generateAnswers = () => {
questions[currentQuestion].answers.forEach(answer => {
const button = document.createElement('button');
button.innerText = answer.answer;
if (answer.correct) {
button.classList.add('right');
}
button.classList.add('btn');
fourAnswers.appendChild(button);
fourAnswers.classList.remove('hide');
})
fourAnswers.onclick = () => {
const selected = event.target;
if (selected.classList.contains('right')) {
selected.classList.add('selectedRight');
count++;
counter.textContent = `Score: ${count}`;
}
if (!selected.classList.contains('right')) {
selected.classList.add('selectedWrong');
}
const rightA = document.querySelector('.right');
rightA.classList.add('selectedRight');
}
currentQuestion++;
if (currentQuestion > questions.length) {
console.log('inside if');
startQuiz.innerText = 'START';
theQuestion.innerText = `Good job! You got ${count} correct answers out of ${questions.length}. Press 'START' to go again.`;
count = 0;
currentQuestion = 0;
}
}
const questions = [
{
question: 'What is the capital of Greece?',
answers: [
{ answer: 'Athens', correct: true },
{ answer: 'Gothenburg', correct: false },
{ answer: 'Madrid', correct: false },
{ answer: 'Berlin', correct: false }
]
},
{
question: 'What is the capital of Sweden?',
answers: [
{ answer: 'Stockholm', correct: true },
{ answer: 'Lisboa', correct: false },
{ answer: 'Paris', correct: false },
{ answer: 'New York', correct: false }
]
},
{
question: 'What is the capital of Portugal?',
answers: [
{ answer: 'Lisboa', correct: true },
{ answer: 'Valencia', correct: false },
{ answer: 'Porto', correct: false },
{ answer: 'London', correct: false }
]
},
{
question: 'What is the capital of Argentina?',
answers: [
{ answer: 'Buenos Aires', correct: true },
{ answer: 'Santiago', correct: false },
{ answer: 'Amsterdam', correct: false },
{ answer: 'Beijing', correct: false }
]
},
{
question: 'What is the capital of Thailand?',
answers: [
{ answer: 'Bangkok', correct: true },
{ answer: 'Manila', correct: false },
{ answer: 'Rome', correct: false },
{ answer: 'Nicosia', correct: false }
]
},
{
question: 'What is the capital of Denmark?',
answers: [
{ answer: 'Copenhagen', correct: true },
{ answer: 'Oslo', correct: false },
{ answer: 'Beirut', correct: false },
{ answer: 'Los Angeles', correct: false }
]
}
]
startQuiz.addEventListener('click', showQuestion);
You should change the line 51 as follows.
if (currentQuestion >= questions.length) {
This is because the questions.length is 6 and the counter currentQuestion is 6 at the last question.
-- edit --
OK, try to modify like the following.
const showQuestion = () => {
if (currentQuestion >= questions.length) {
console.log('inside if');
startQuiz.innerText = 'START';
theQuestion.innerText = `Good job! You got ${count} correct answers out of ${questions.length}. Press 'START' to go again.`;
count = 0;
currentQuestion = 0;
return;
}
startQuiz.innerText = 'NEXT';
while (fourAnswers.firstChild) {
fourAnswers.removeChild(fourAnswers.firstChild);
}
theQuestion.innerText = questions[currentQuestion].question;
checkCounters();
currentQuestion++;
}
And delete currentQuestion++; from generateAnswers()
Difficult to be sure without a full reproducible jsfiddle, but I think it should be:
if (currentQuestion >= questions.length)
instead of:
if (currentQuestion > questions.length)
Put your if in the beginning of the function and return inside of it so that you exit the function and do nothing else once you go into the if.
Currently what's happening is that the foreach is triggered even when the currentQuestion has passed the questions.length.
Also add the check to be more or equal.
const generateAnswers = () => {
if (currentQuestion >= questions.length) {
console.log('inside if');
startQuiz.innerText = 'START';
theQuestion.innerText = `Good job! You got ${count} correct answers out of ${questions.length}. Press 'START' to go again.`;
count = 0;
currentQuestion = 0;
return; // end the function here so the other logic doesn't trigger.
}
questions[currentQuestion].answers.forEach(answer => {
const button = document.createElement('button');
button.innerText = answer.answer;
if (answer.correct) {
button.classList.add('right');
}
button.classList.add('btn');
fourAnswers.appendChild(button);
fourAnswers.classList.remove('hide');
})
fourAnswers.onclick = () => {
const selected = event.target;
if (selected.classList.contains('right')) {
selected.classList.add('selectedRight');
count++;
counter.textContent = `Score: ${count}`;
}
if (!selected.classList.contains('right')) {
selected.classList.add('selectedWrong');
}
const rightA = document.querySelector('.right');
rightA.classList.add('selectedRight');
}
currentQuestion++;
}

How Object inside Objects generate random item?

i have a object inside objects that want to generate randomly, is a questions quiz. i have tried var
index = Math.floor(Math.random() *currentQuestion.answers[letter]);
no luck with it, can anyone help and explain why i cant generate the item randomly ?
i need to generate random answers item inside my createQuiz function, now is fixed exp: A: MJ, B:Pippen, C:Magic and if refresh it will randomly generate A:Pippen B:Magic C:MJ and so on.
my objects variable
const myQuestions = [
{
question: "What's my name ?",
answers: {
item1: "Chris",
item2: "Leborn",
item3: "Webber"
},
correctAnswer: "Chris",
button: "Next"
},
{
question: "What's my age ?",
answers: {
item1: "31",
item2: "30",
item3: "29"
},
correctAnswer: "31",
button: "Next"
},
{
question: "What's my favor NBA star ?",
answers: {
item1: "MJ",
item2: "Pippen",
item3: "Magic"
},
correctAnswer: "MJ",
button: "Done"
}
]
functions
function createQuiz() {
//clear the contents of questions div first
document.getElementById('questionsBox').innerHTML = "";
//clear answers box
document.getElementById('answersBox').innerHTML = "";
//set answer sting
answersCaptcha = [];
//output
output = [];
// for each question...
myQuestions.forEach(
(currentQuestion, questionNumber) => {
// we'll want to store the list of answer choices
const answers = [];
const option = ["A","B","C"];
let count = -1;
for(letter in currentQuestion.answers){
count++;
var index = Math.floor(Math.random() * currentQuestion.answers[letter]);
// i need to generate random answers item here, now is fixed
// exp: A: MJ, B:Pippen, C:Magic and if refresh it will randomly generate A:Pippen B:Magic C:MJ and so on.
answers.push(
`<label>
<input type="radio" name="question${questionNumber}" value="${letter}">
${option[count]} :
${currentQuestion.answers[letter]}
</label>`
);
}
// add this question and its answers to the output
output.push(
`<div class="question"> ${currentQuestion.question} </div>
<div class="answers"> ${answers.join('')} </div>`
);
})
document.getElementById('answersBox').innerHTML = output.join('');
console.log(answersCaptcha);
}
if i understand your question correctly... all you need is shuffle(currentQuestion.answers)
The problem is that letter is not a number - it cannot be used to randomize that way. Here's a solution:
const myQuestions = [{
question: "What's my name ?",
answers: {
item1: "Chris",
item2: "Leborn",
item3: "Webber"
},
correctAnswer: "Chris",
button: "Next"
},
{
question: "What's my age ?",
answers: {
item1: "31",
item2: "30",
item3: "29"
},
correctAnswer: "31",
button: "Next"
},
{
question: "What's my favor NBA star ?",
answers: {
item1: "MJ",
item2: "Pippen",
item3: "Magic"
},
correctAnswer: "MJ",
button: "Done"
}
]
createQuiz()
function createQuiz() {
//clear the contents of questions div first
document.getElementById('questionsBox').innerHTML = "";
//clear answers box
document.getElementById('answersBox').innerHTML = "";
//set answer sting
answersCaptcha = [];
//output
output = [];
// for each question...
myQuestions.forEach(
(currentQuestion, questionNumber) => {
// we'll want to store the list of answer choices
const option = ["A", "B", "C"];
let count = -1;
const answers = shuffleArray(Object.entries(currentQuestion.answers)).map((e, i) => {
return `<label>
<input type="radio" name="question${questionNumber}" value="${e[0]}">
${option[i]} :
${currentQuestion.answers[e[0]]}
</label>`
})
// add this question and its answers to the output
output.push(
`<div class="question"> ${currentQuestion.question} </div>
<div class="answers"> ${answers.join('')} </div>`
);
})
document.getElementById('answersBox').innerHTML = output.join('');
console.log(answersCaptcha);
}
function shuffleArray(arr) {
let array = arr
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array
}
<div id="questionsBox"></div>
<div id="answersBox"></div>

How to make multiple selects with the value from the previous select

Kinda new here and new to javascript
So I am trying to make a chained select in javascript and I want to create different select from the value in the previous one, it seems easy to make two, but what about three selects or more?
Here's some of my code that I tried
function firstSelect(){
var x = document.getElementById('firstSelect').value;
switch(x){
case 'Food':
localStorage.setItem('first','food');
add(food, 'secondSelect');
document.getElementById('secondSelect').setAttribute('onchange', secondSelect());
//alert(x);
break;
//more code to be added
}
//trying to add a second select?
function secondSelect(){
var x = document.getElementById('secondSelect').value;
var first = localStorage.getItem('first');
if(first == 'food'){
for(i=0, i<food.length; i++){
if( x == food[i].value ){
add(food[i].value, 'thirdSelect');
}
}
}//more code to be added but this is the gist of it
}
//function to populate each select added
function add(itemArray, name){
if(document.getElementById(name)){
var element = document.getElementById(name);
element.parentNode.removeChild(element);
}
var list = document.createElement('select');
list.setAttribute('id', name);
var length = itemArray.length
var opt;
for(i=0;i<length;i++){
opt = document.createElement('option');
opt.setAttribute('value', itemArray[i].value);
opt.text = itemArray[i].text;
list.appendChild(opt);
}
document.body.appendChild(list);
}
//here's how my data looks like
var food = [
{value: "meat", text: "Meat"},
{value: "vege", text: "Vegetables"},
{value: "fruit", text: "Fruits"}
];
var meat = [
{value: "beef", text: "Beef"},
{value: "chicken", text: "Chicken"},
{value: "pork", text: "Pork"}
];
var vege = [
{value: "broccoli", text: "Broccoli"},
{value: "spinach", text: "Spinach"},
{value: "carrort", text: "Carrots"}
];
Basically I make different functions for the different level of select I make, not sure if I should do it like that but... if food is the first choice and meat/vege will be shown as the second select accordingly, so am I going to make another function for my third select(beef?chicken?pork?)
I was thinking about putting up one function for creating multiple selects but I am not sure how to write it as an onchange function, it seems to me that each onchange function for different select should be different too...?
Is there an efficient way of doing this?
Please
// This is just a map
// for selecting value for the child selector when the parent is changed.
// Parents value are the keys here
// the data to be passed to the child according to the current value of parent are stored in the array
// Map for first chain
const datamap_1 = {
1: [{
value: 10,
text: 'ten'
},
{
value: 12,
text: 'twelve'
}
],
2: [{
value: 20,
text: 'twenty'
},
{
value: 21,
text: 'twelve'
}
],
3: [{
value: 30,
text: 'thirty'
},
{
value: 31,
text: 'thirty one'
}
]
};
// Map for second chain chain
const datamap_2 = {
10: [{
value: 100,
text: 'hundred'
},
{
value: 101,
text: 'hundred and one'
}
],
20: [{
value: 200,
text: 'two hundred'
},
{
value: 201,
text: 'two hundred and one'
}
]
};
class ChainedSelect{
constructor (parent, child, map){
this.parent = parent;
this.child = child;
this.data_map = map;
this._attach_events();
this._init();
}
// Just for selecting the first value in parent selected by default and initlizing the child
_init(){
const val = this.parent.value;
this._make_options(this.data_map[val]);
}
// All the event handling goes here
_attach_events(){
this.parent.onchange = (function(_this){
return function(){
const val = this.value;
_this._make_options(_this.data_map[val]);
}
})(this)
}
// helper function for creating options from a passed array
_make_options(data){
this.child.innerHTML = '';
if (!data) return ;
for(let i=0;i < data.length;i++){
let opt = data[i];
let el = document.createElement("option");
el.textContent = opt.text;
el.value = opt.value;
this.child.appendChild(el);
this.child.dispatchEvent(new Event('change'));
}
}
}
window.onload = function(){
const first = document.getElementById('first');
const second = document.getElementById('second');
const third = document.getElementById('third');
const chain_1 = new ChainedSelect(first, second, datamap_1);
const chain_2 = new ChainedSelect(second, third, datamap_2);
}
here is a working jsfiddle https://jsfiddle.net/jgv8pd5k/

Javascript array variable as property

If I access an array as a property, can I still access its inner indices by saying theArray[index]?
Here's the code I have:
function Dictionary() {
var articles, verbs, nouns, prepositions, adjectives, words;
articles = ["the", "a"];
verbs = ["shoot", "break", "amaze", "grope", "destroy", "bark", "stream", "flow"];
nouns = ["journal", "bird", "library", "book", "calculator", "pen", "evening"];
prepositions = ["while", "although", "beside", "by", "in", "outside", "like", "into"];
adjectives = ["white", "black", "blue", "red", "bright", "calm", "cheerful", "clumsy", "cloudy"];
words = [articles, verbs, nouns, prepositions, adjectives];
Object.defineProperties(this, {
"theWords": {
get: function() {return words;}
},
"theArticles": {
get: function() {return articles;}
},
"theVerbs": {
get: function() {return verbs;}
},
"theNouns": {
get: function() {return nouns;}
},
"thePrepositions": {
get: function() {return prepositions;}
},
"theAdjectives": {
get:function() {return adjectives;}
}
});
}
In another Javascript function called Poem, I want to access Dictionary's property "theWords". I am doing this by saying something like
var theDictionary = new Dictionary();
var articles = theDictionary.theWords[0];
Is this possible? Furthermore, can I access the specific indices inside the array "articles" by saying something like:
var article1 = theDictionary.theWords[0][0];
Is this possible? My code is giving me an error, but I am not sure what is wrong.
This is what I would do:
function Dictionary(user){
/* It's less code to write `var` repeatedly than the variable name twice
in this case. You can save keystrokes, avoid pressing shift, by using
single quotes. That does not mean your code was not clear or didn't work
*/
this.currentUser = user;
this.articles = ['the', 'a'];
this.verbs = ['shoot', 'break', 'amaze', 'grope', 'destroy', 'bark', 'stream', 'flow'];
this.nouns = ['journal', 'bird', 'library', 'book', 'calculator', 'open', 'evening'];
this.prepositions = ['while', 'although', 'beside', 'by', 'in', 'outside', 'like', 'into'];
this.adjectives = ['white', 'black', 'blue', 'red', 'bright', 'calm', 'cheerful', 'clumsy', 'cloudy'];
this.words = [this.articles, this.verbs, this.nouns, this.prepositions, this.adjectives];
// you don't really need this function - just an example of a Constructor function
this.wordsToString = function(){
var wds = this.words, a = [];
for(var i in wds){
a.concat(wds[i])
}
return a.join();
}
}
var dic = new Dictionary('Bob');
console.log(dic.currentUser); console.log(dic.articles[0]);
console.log(dic.wordsToString());
Now you can change the articles property, or whatever:
dic.articles = ['test1', 'test2'];
console.log(dic.wordsToString());

Categories

Resources