Abstraction with "this" variable - javascript

I am making a crossword puzzle. Here is one example of one box of the crossword:
<div class="grid-item">M
<input id="input-item24" type = "text" size= "4">
</div>
<div class="grid-item-black"></div>
</div>
<button id='total_score'>Click to see score</button>
<div id='display_score'></div>
Here is the script for each box:
<script
src="https://code.jquery.com/jquery-3.2.1.js"
integrity="sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE="
crossorigin="anonymous"></script>
<script src="script.js"></script>
<script>
var answers = [];
answers[2]= 'M';
answers[3]= 'E';
answers[4]='A';
answers[5]= 'L';
answers[6]= 'B';
answers[7]= 'E';
answers[8]='L';
answers[9]= 'L';
answers[10]= 'A';
answers[11]= 'I';
answers[12]= 'L';
answers[13]= 'I';
answers[14]= 'A';
answers[15]= 'D';
answers[16]= 'R';
answers[17]= 'E';
answers[18]= 'T';
answers[19]= 'R';
answers[20]= 'Y';
answers[21]= 'D';
answers[22]= 'E';
answers[23]= 'E';
answers[24]= 'M';
var score = 0;
var total = 0;
I want to abstract (condense it) the calculate function by using the "THIS variable" to calculate the score. This way, I won't have to repeat this code (which works) for every box:
$('#total_score').on('click',calculate);
function calculate(){
if($('#input-item21').val()==answers[21]){total = total +1;}
$('#display_score').html(total);
};
So far I have tried this, why won't it work?
$('.grid-item').on('click',calculate);
function calculate (){
if($(this).html() == '')
{
$(this).html(answers);
if(answers == '#input-item')
{score = score + 1;};
}
$('#display_score').html(score);}
};

You should just need to reference the value and id, like this (i would also make it a blur event so that the calculation occurs after the user enters info in the square):
$('.grid-item').on('blur',calculate);
function calculate (){
let input = $(this)[0]
if(input.value || input.value !== '') {
let id = /\d+$/.match(input.id)[0];
if(id && answers[id] === input.value) {
score++;
}
$('#display_score').html(score);
}
};
You can also pass the event target to your calculate function, such as below. I would consider further simplifying things by changing the input ids to just numbers (e.g. #input_item24 to just 24) and then using that to check the answers array by passing the click target to the calculate function:
$('.grid-item').on('blur', e => { calculate(e.target) });
function calculate (input){
if(input.value || input.value !== '') {
if(answers[Number(input.id)] === input.value) {
score++;
}
$('#display_score').html(score);
}
};

Related

Javascript - how to use the same onkeydown event to call different functions on different occasions

I am trying to make a simple quizz with random questions.
After typing in the answer, the user can press the Enter key to check whether his answer is correct or wrong. In that moment the textbox will be hidden.
I would like the user to be able to press the Enter key again to proceed to the next question. But how can I do that, since I already have a function being called with that key?
This is what I am working with:
var country = ["Italy", "Spain", "Portugal", "France"];
var capital = ["rome", "madrid", "lisbon", "paris"];
var random001 = Math.floor(Math.random() * country.length);
document.getElementById("country").innerHTML = country[random001];
function submit001() {
var b = input001.value.toLowerCase();
var text;
switch (true) {
case random001 == 0 && b == capital[0]:
case random001 == 1 && b == capital[1]:
case random001 == 2 && b == capital[2]:
case random001 == 3 && b == capital[3]:
text = "Correct!";
hideAndShowDIV();
break;
default:
text = input001.value.bold() + " is not correct!";
document.getElementById("input001").value = "";
}
document.getElementById("answer001").innerHTML = text;
}
function hideAndShowDIV() {
var x = document.getElementById("userInput");
if (x.style.display === "none") {
x.style.display = "block";
} else {
x.style.display = "none";
}
}
function goToNextQuestion() {
random001 = Math.floor(Math.random() * country.length);
document.getElementById("country").innerHTML = country[random001];
hideAndShowDIV()
document.getElementById("input001").focus();
}
<p id="message001">What is the capital of <text id="country"></text>?</p>
<div id="userInput" style="display:block">
<input type="text" id="input001" autofocus onKeyDown="if(event.keyCode==13) submit001();">
</div>
<p id="answer001"></p>
The function goToNextQuestion() is what I want to call with the Enter key.
A quick solution would be to add an extra line in the function submit001, before any of the code it already has:
if (document.getElementById("answer001").textContent === "Correct") {
goToNextQuestion();
}
And in the function gotoNextQuestion you should then make sure to clear that text content (remove "Correct").
But be aware the the keydown event does not trigger on the input element when you hide it, so you should listen to that event on the document.
Better still would be to use a variable for that state, instead of depending on what is in the HTML document.
Here is an implementation that uses addEventListener instead of having JS code inside your HTML tags. Note how it listens on the document level. Also better to use the event key property and not keyCode.
A new variable execOnEnter defines which function to execute depending on the state of the "game". It is changed in the code to either submit001 or goToNextQuestion:
var country = ["Italy", "Spain", "Portugal", "France"];
var capital = ["rome", "madrid", "lisbon", "paris"];
var random001 = Math.floor(Math.random() * country.length);
document.getElementById("country").textContent = country[random001];
var execOnEnter = submit001;
document.addEventListener("keydown", function (e) {
if (e.key !== "Enter") return;
execOnEnter();
});
function submit001() {
var b = input001.value.toLowerCase();
var text;
if (b === capital[random001]) {
text = "Correct!";
hideAndShowDIV();
execOnEnter = goToNextQuestion;
} else {
text = input001.value.bold() + " is not correct!";
}
document.getElementById("input001").value = "";
document.getElementById("answer001").innerHTML = text;
}
function hideAndShowDIV() {
var x = document.getElementById("userInput");
if (x.style.display === "none") {
x.style.display = "block";
} else {
x.style.display = "none";
}
}
function goToNextQuestion() {
document.getElementById("answer001").innerHTML = "";
random001 = Math.floor(Math.random() * country.length);
document.getElementById("country").innerHTML = country[random001];
hideAndShowDIV()
execOnEnter = submit001;
document.getElementById("input001").focus();
}
<p id="message001">What is the capital of <text id="country"></text>?</p>
<div id="userInput" style="display:block">
<input type="text" id="input001" autofocus>
</div>
<p id="answer001"></p>
You could check inside the the submit001() function to check if the userInput control is hidden and if so, direct go to the goToNextQuestion.
There are a few ways you could do this. I would recommend not using this approach though, as hitting the enter button to submit is not intuitive. I would recommend adding a 'submit' button. Regardless you could try this:
var country = ["Italy", "Spain", "Portugal", "France"];
var capital = ["rome", "madrid", "lisbon", "paris"];
var random001 = Math.floor(Math.random() * country.length);
document.getElementById("country").innerHTML = country[random001];
function input001_onKeyDown(e){
if(e.keyCode === 13){
submit001();
} else {
clearParagraph();
}
}
function submit001() {
var b = input001.value.toLowerCase();
document.querySelector("#input001").value = ''
var text;
switch (true) {
case random001 == 0 && b == capital[0]:
case random001 == 1 && b == capital[1]:
case random001 == 2 && b == capital[2]:
case random001 == 3 && b == capital[3]:
text = "Correct! " + b + " is the capital of " + country[random001];
goToNextQuestion();
break;
default:
text = b + " is not correct!";
document.getElementById("input001").value = "";
}
document.getElementById("answer001").innerHTML = text
}
function hideAndShowDIV() {
var x = document.getElementById("userInput");
if (x.style.display === "none") {
x.style.display = "block";
} else {
x.style.display = "none";
}
}
function goToNextQuestion() {
random001 = Math.floor(Math.random() * country.length);
document.getElementById("country").innerHTML = country[random001];
document.getElementById("input001").focus();
}
function clearParagraph(){
document.querySelector("#answer001").innerHTML = ''
}
<p id="message001">What is the capital of <text id="country"></text>?</p>
<div id="userInput" style="display:block">
<input type="text" id="input001" autofocus onKeyDown="input001_onKeyDown(event)">
</div>
<p id="answer001"></p>
Without going into too much detail (I'd have to understand a lot more about the behavior that you would want in various scenarios . . . Example: Can the user go back to a previous question, change the answer, and revalidate?), I'd suggest some kind of indicator attached to or associated with (i.e., on a close ancestor element) the input to track whether or not an answer has been validated yet.
For example, you could us a class (e.g., class="answer-validated") or a data attribute (e.g., data-validated="true") if the user has already hit Enter to check the value. In the case of a class, it would be absent by default and the "validation" code would add it as part of its logic. Similarly, the data attribute could default to "false" and be updated to "true" when the answer is validated.
The benefit of this approach is that it allows you to directly control the tracking of the input state and, potentially reuse it for other functionality, including:
resetting the input state, if the user wants to re-answer,
easily resetting the state of all questions, without having to refresh/reset the page
applying visual differences with CSS, based on the presence of the class or state of the data attribute
track the number of answers that have been checked and place a limit on the maximum number of validations (i.e., "You can only check 3 answers before submitting your answers")
Etc.
It's possible to infer the state from other elements on the page, but I would argue that it would make more sense to manage the state of the input on itself, rather than determining it based on the state of another page element. It's more straightforward and less prone to having changes to the other elements impact the functionality of the input.

Global variable in number guessing game

I'm trying to make the standard number guessing game that MDN provides in their JS tutorial. I tried tweaking it a bit making different functions for the different scenarios.
It seems the global variable var userGuess = parseInt(guessField.value) is not working as your previous guess always comes up as NaN.
Also when the game resets the showWin() and showLoss() functions work but not the showError() function.
I am very new to JS and coding in general so there is most likely a silly mistake somewhere, if anyone could help me on this problem, that would be greatly appreciated!
var randNum = Math.floor(Math.random() * 100) + 1;
var guessField = document.querySelector('.guessField');
var guessSubmit = document.querySelector('.guessSubmit');
var guesses = document.querySelector('.guesses');
var lastResult = document.querySelector('.lastResult');
var lowOrHi = document.querySelector('.lowOrHi');
var guessCount = 1;
var resetButton;
var userGuess = parseInt(guessField.value);
function checkGuess() {
if(guessCount === 1) {
guesses.textContent = "Previous Guesses: ";
}
guesses.textContent += userGuess + ' ';
if(userGuess === randNum) {
showWin();
} else if(guessCount === 10) {
showLoss();
} else {
showError();
}
guessCount++;
guessField.value = '';
guessField.focus();
}
guessSubmit.addEventListener('click', checkGuess);
function showWin() {
lastResult.textContent = 'You won nice job schmuck';
lastResult.style.backgroundColor = 'green';
gameOver();
}
function showError() {
lastResult.textContent = 'Sorry, wrong guess';
if(userGuess > randNum) {
lowOrHi.textContent = 'Your guess was too high';
} else if(userGuess < randNum) {
lowOrHi.textContent = 'Your guess was too low';
}
}
function showLoss() {
lastResult.textContent = 'You lost, you schmuck';
lastResult.style.backgroundColor = 'red';
gameOver();
}
function gameOver() {
guessField.disabled = true;
guessSubmit.disabled = true;
resetButton = document.createElement('button');
resetButton.textContent = 'New Game';
document.body.appendChild(resetButton);
resetButton.addEventListener('click', resetGame);
}
function resetGame() {
guessCount = 1;
var resetParas = document.querySelectorAll('.resultParas');
for(i = 0; i < resetParas.length; i++) {
resetParas[i].textContent = '';
}
guessField.disabled = false;
guessSubmit.disabled = false;
resetButton.parentNode.removeChild(resetButton);
lastResult.style.backgroundColor = 'white';
randNum = Math.floor(Math.random() * 100) + 1;
}
<h1>Guessing Game</h1>
<p>Type in a number between 1 and 100 and I will tell you if it is too high or low.</p>
<form>
<label for="guessField">Enter a guess: </label>
<input type="text" id="guessField" class="guessField"/>
<input type="button" value="Submit Guess" class="guessSubmit"/>
</form>
<div class='resultParas'>
<p class="guesses"></p>
<p class="lastResult"></p>
<p class="lowOrHi"></p>
</div>
In your script, you call
parseInt(guessField.value) // effectively that is parseInt("") when it's empty
Calling parseInt() with an empty string returns NaN;
MDN in their example use:
var userGuess = Number(guessField.value);
Number("") returns a 0 number value.
You would also need to update the value of userGuess every time you call checkGuess(). So the alterations you need are:
// ... code
var userGuess = Number(guessField.value);
// ... the rest of code
function checkGuess() {
userGuess = Number(guessField.value)
// ... rest of code
}
// rest of code
You don't have to use Number() of course, you could also do some other condition checking, but Number() is an elegant way of accepting either numbers or an empty string.
UPDATE
New jsbin here.
For the resetGame() part: you were selecting the .resultParas like:
var resetParas = document.querySelectorAll('.resultParas');
Then you iterated over the results of that and replaced the .textContent of those elements. But those were not simple text nodes, they were parapgraph nodes with text nodes inside them. I changed it to:
var resetParas = document.querySelector('.resultParas').children;
It should be working! I've put some comments in the jsfiddle for more explanation.

addEventListener ('click', ...) By clicking on 'numbers' button text on the display should be equal to that number

I am trying to build a javascript calculator as per freecodecamp requirement on front end track.
Codepen link: https://codepen.io/ekilja01/pen/MoXROe
By pressing any number on calculator the display must be changed to that number. Also the var number has to remember its value, because by clicking the number again it becomes a new number, so after that I can use operators and parseInt to evaluate and display the final value. I've got an idea how can this be implemented by using jQuery, so something like this:
$("#numbers a").not("#clear,#clearall").click(function(){
number += $(this).text();
totaldiv.text(number);
});
$("#operators a").not("#equals").click(function(){
operator = $(this).text();
newnumber = number;
number = "";
totaldiv.text("0");
});
$("#clear,#clearall").click(function(){
number = "";
totaldiv.text("0");
if ($(this).attr("id") === "clearall") {
newnumber = "";
}
});
//Add your last .click() here!
$("#equals").click(function(){
if (operator === "+"){
number = (parseInt(number, 10) + parseInt(newnumber,10)).toString(10);
} else if (operator === "-"){
number = (parseInt(newnumber, 10) - parseInt(number,10)).toString(10);
} else if (operator === "÷"){
number = (parseInt(newnumber, 10) / parseInt(number,10)).toString(10);
} else if (operator === "×"){
number = (parseInt(newnumber, 10) * parseInt(number,10)).toString(10);
}
totaldiv.text(number);
number = "";
newnumber = "";
});
But what can be equivalent to this in vanilla js? I've tried .textContent or .innerHTML, but neither of them work. On calculator's dispay either undefined or <a>1</a> ... <a>AC</a>. Also what is the equivalent to .not(). Any help will be much appreciated.
html:
<div id="calculator">
<p id="cal">CALCULATOR</p>
<div id="total">
</div>
<div id="operators">
<a>÷</a>
<a>×</a>
<a>+</a>
<a>-</a>
<a>=</a>
</div>
<div id="numbers">
<a>1</a>
<a>2</a>
<a>3</a>
<a>4</a>
<a>5</a>
<a>6</a>
<a>7</a>
<a>8</a>
<a>9</a>
<a id="clear">C</a>
<a>0</a>
<a id="clearall">AC</a>
</div>
</div>
js:
document.addEventListener('DOMContentLoaded', function (event) {
console.log('DOM OK');
// Declare variables for number, new number and operators
var number, newNumber, operator = "";
// Declare variable total number to display total on the calculator's display
var totalNumber = document.getElementById("total");
totalNumber.textContent = "0";
document.getElementById("numbers").addEventListener('click', function(){
number += this.textContent;
totalNumber.textContent = number;
})
})
You may want to use the event argument in the callback function. For example:
number = "";
document.getElementById("numbers").addEventListener('click', function(e){
number += e.target.textContent;
totalNumber.textContent = number;
})
More information on the event target properties specifically:
https://developer.mozilla.org/en-US/docs/Web/API/Event/target
Entire JS code:
document.addEventListener('DOMContentLoaded', function (event) {
console.log('DOM OK');
// Declare variables for number, new number and operators
var number, newNumber, operator = "";
// Declare variable total number to display total on the calculator's display
var totalNumber = document.getElementById("total");
totalNumber.textContent = "0";
number = "";
document.getElementById("numbers").addEventListener('click', function(e){
number += e.target.textContent;
totalNumber.textContent = number;
})
})
just to display which number is clicked on-
var totalNumber = document.getElementById("total");
totalNumber.textContent = "";
document.getElementById("numbers").addEventListener('click', function(e){
totalNumber.textContent += e.target.innerText;
})

Guessing Game in Javascript

What I'm trying to do is make a simple guessing game where the user can any the number without limit but will be graded at the end when the guessed the number correctly based on the number of their guesses. However, with my code when I enter a number and press the button to multiple times the hint changes from "Higher" to "Lower" even if the number is not changed also the message that should be displayed when the number is guessed correctly is not showing. Here's my code, I'm a beginner so there's bound to be errors in the code and any help is appreciated.
<fieldset>
<input type="number" id="guess" />
<button onClick="checknum();">Check Number</button>
</fieldset>
<fieldset>
<p>Your current status:</p>
<output id="status_output">You have yet to guess anything.</output>
</fieldset>
<script type="text/javascript">
function checknum(){
var randomNumber = Math.floor((Math.random() * 100) + 1);
var guessNumber = document.getElementById("guess").value;
//var guessNumber = parseInt(guess.value);
var statusOutput = document.getElementById('status_output');
var counter = 0;
var isguessed = false;
do {
counter = (counter + 1)
if (guessNumber < randomNumber) {
statusOutput.value = ("Higher");
}
else if (guessNumber > randomNumber) {
statusOutput.value = ("Lower");
}
else if (guessNumber = randomNumber) {
set (isguessed = true());
statusOutput.value = ("Correct" + mark());
}
}
while (isguessed = false);
}
function mark(){
if (counter < 10){
statusOutput.value("Excellent");
}
else if (counter > 10 && counter <20){
statusOutput.value("Okay");
}
else
statusOutput.value("Needs Practice");
}
</script>
In your while you are assigning false to the variable named isguessed.
You want to do while (isguessed === false) instead. This will check if isguessed is set to false
isguessed = false : assigns the varibles isguessed to false
isguessed === false : a boolean expression return true or false

Limit number of lines in textarea and Display line count using jQuery

Using jQuery I would like to:
Limit the number of lines a user can enter in a textarea to a set number
Have a line counter appear that updates number of lines as lines are entered
Return key or \n would count as line
$(document).ready(function(){
$('#countMe').keydown(function(event) {
// If number of lines is > X (specified by me) return false
// Count number of lines/update as user enters them turn red if over limit.
});
});
<form class="lineCount">
<textarea id="countMe" cols="30" rows="5"></textarea><br>
<input type="submit" value="Test Me">
</form>
<div class="theCount">Lines used = X (updates as lines entered)<div>
For this example lets say limit the number of lines allowed to 10.
html:
<textarea id="countMe" cols="30" rows="5"></textarea>
<div class="theCount">Lines used: <span id="linesUsed">0</span><div>
js:
$(document).ready(function(){
var lines = 10;
var linesUsed = $('#linesUsed');
$('#countMe').keydown(function(e) {
newLines = $(this).val().split("\n").length;
linesUsed.text(newLines);
if(e.keyCode == 13 && newLines >= lines) {
linesUsed.css('color', 'red');
return false;
}
else {
linesUsed.css('color', '');
}
});
});
fiddle:
http://jsfiddle.net/XNCkH/17/
Here is little improved code. In previous example you could paste text with more lines that you want.
HTML
<textarea data-max="10"></textarea>
<div class="theCount">Lines used: <span id="linesUsed">0</span></div>
JS
jQuery('document').on('keyup change', 'textarea', function(e){
var maxLines = jQuery(this).attr('data-max');
newLines = $(this).val().split("\n").length;
console.log($(this).val().split("\n"));
if(newLines >= maxLines) {
lines = $(this).val().split("\n").slice(0, maxLines);
var newValue = lines.join("\n");
$(this).val(newValue);
$("#linesUsed").html(newLines);
return false;
}
});
For React functional component that sets new value into state and forwards it also to props:
const { onTextChanged, numberOfLines, maxLength } = props;
const textAreaOnChange = useCallback((newValue) => {
let text = newValue;
if (maxLength && text.length > maxLength) return
if (numberOfLines) text = text.split('\n').slice(0, numberOfLines ?? undefined)
setTextAreaValue(text); onTextChanged(text)
}, [numberOfLines, maxLength])
A much ugly , but somehow working example
specify rows of textarea
<textarea rows="3"></textarea>
and then
in js
$("textarea").on('keydown keypress keyup',function(e){
if(e.keyCode == 8 || e.keyCode == 46){
return true;
}
var maxRowCount = $(this).attr("rows") || 2;
var lineCount = $(this).val().split('\n').length;
if(e.keyCode == 13){
if(lineCount == maxRowCount){
return false;
}
}
var jsElement = $(this)[0];
if(jsElement.clientHeight < jsElement.scrollHeight){
var text = $(this).val();
text= text.slice(0, -1);
$(this).val(text);
return false;
}
});
For the React fans out there, and possibly inspiration for a vanilla JS event handler:
onChange={({ target: { value } }) => {
const returnChar = /\n/gi
const a = value.match(returnChar)
const b = title.match(returnChar)
if (value.length > 80 || (a && b && a.length > 1 && b.length === 1)) return
dispatch(setState('title', value))
}}
This example limits a textarea to 2 lines or 80 characters total.
It prevents updating the state with a new value, preventing React from adding that value to the textarea.

Categories

Resources