My if statement gives me the wrong output - javascript

window.onload = start;
function start() {
document.getElementById("kalk").onclick = find;
find(1, 9999);
}
function find(min, max) {
var factor = document.getElementById("tall").value;
var factor2 = document.getElementById("tall2").value;
var x = factor * factor2;
document.getElementById("utskrift").innerHTML = x;
if (x >= min && x <= max) {
document.getElementById("msg").innerHTML = "Number is in interval."
} else {
document.getElementById("msg").innerHTML = "Number is not in interval."
}
}
<h2>Gang to tall</h2>
T1 <input type="number" id="tall" /> T2 <input type="number" id="tall2" />
<button id="kalk">Finn tall i intervall</button> Sum: <span id="utskrift"></span>
<p id="msg"></p>
So by reading this code.. what Im trying to do is have two inputs where i multiply the numbers typed in them. In my "Find()" Parameter i have two arguments that says the numbers should be between 1-9999. In my "function find" i called these arguments min and max. Further down the code Im asking if the output is between these numbers are between min and max give "Number is in interval". The problem is that when i even when the numbers are in these arguments i get my else statement. Is there anyway to fix this or put an input felt in the parameter?
Thanks

You are attaching the function find directly to the click event listener. The function is expecting two parameters min and max:
function find(min, max)
But when a click happens, it recieves, being an event listener, only one parameter which is the event object. Thus min is going to be an event object, and max will be undefined and your if statement won't work. You can check this out by logging min and max inside find to the console.
Wrap the function find call in another function and attach the latter as the event listener:
document.getElementById("kalk").onclick = function(event) { // the event listener function recieves the event object as the only parameter (not that argument event here is not necessary, I just added it for explanation)
find(1, 90000); // when this function get executed (when a click happens) then call find with proper parameters
}

Well, first you have to remember that the "T" in HTML stands for "text". There is only one data type in HTML... strings. When you get a value from HTML and want to use it as a number in JavaScript, you have to convert it to a number.
Next, you don't really have your functions organized correctly. find shouldn't just run as soon as the page is loaded. You want it to run when the button is clicked. And that way, the min and max values are passed at the moment you need them.
Lastly, you HTML statically says "Sum" when, you are in fact calculating a product.
See other best practice items in the comments.
<html>
<head>
</head>
<body>
<h2>Gang to tall</h2>
T1 <input type="number" id="tall"/>
T2 <input type="number" id="tall2"/>
<button id="kalk">Finn tall i intervall</button>
Product: <span id="utskrift"></span>
<p id="msg"></p>
<!-- It's a good idea to place your scripts just before the closing
body tag. That way, the HTML has been loaded by the time the
JavaScript runs. -->
<script>
document.getElementById("kalk").onclick = result;
// Get your DOM references just once so you don't have to re-scan the DOM
// for them every time the button is clicked.
// Also, just reference the elements themselves, not properties (.value) of
// the element because if you just reference the property and later decide
// you need some other property, you'll have to re-scan the DOM for the same
// element again. This way, you maintain a reference to the element and can
// get any property you need at any time.
var factor = document.getElementById("tall");
var factor2 = document.getElementById("tall2");
var product = document.getElementById("utskrift");
var msg = document.getElementById("msg");
function find(min,max){
// All values coming from HTML are strings. You should always
// explicitly convert them to numbers when numbers are expected.
var x = parseInt(factor.value, 10) * parseInt(factor2.value,10);
// .innerHTML is for when you are assigning a string that contains HTML.
// It tells the HTML parser to parse the string for HTML. If you are not
// including HTML, use .textContent, which doesn't do this extra parsing.
product.textContent = x;
if (x >= min && x <= max) {
msg.textContent = "Number is in interval."
} else {
msg.textContent = "Number is not in interval."
}
}
function result(){
find(1, 9999);
}
</script>
</body>
</html>

Related

How to create flexible input-fields in Javascript?

I am having problems to create the following situation:
I want to create inputfields, where I can write "1", "2" or "3" in any order, but each number is only allowed to be writen once.
Repeating one of those three numbers and writing other numbers than those three in the inputfields should be considered bad.
What do I need to add to the code?
a = 1
b = 2
c = 3
L = a
L = b
L = c
function F1() {
feedBack = document.getElementById("feedBack");
an = document.getElementById("userAnswer");
L = document.getElementById("L").textContent;
if (an == L) {
feedBack.textContent = "good";
} else {
feedBack.textContent = "bad";
}
}
<input id="userAnswer" type=text>
<input id="userAnswer" type=text>
<input id="userAnswer" type=text>
<button onclick="F1()">check</button>
<label id="L"> </label>
<p id="feedBack"> </p>
It's worth mentioning (especially for the more novice JavaScripters reading this) that, as with most programming problems, there are lots of ways to correctly satisfy the requirements from the original question.
I know this isn't Code Review.SE, but you might benefit from some feedback on the example you provided, if you're interested:
The variables a, b, and c are never used in any particularly useful way, and detract from the human readability of your code. You should probably remove their declarations entirely.
You set the variable L three times consecutively. This doesn't do very much at all, considering also L is overridden once F1() is executed. You should probably remove these unnecessary assignments entirely.
The HTML specification is clear that ID values should be unique across the entire document space; anything else is invalid and can lead to headaches and undocumented behavior down the line, especially when JavaScript comes into play. In the simplified example, there really isn't any need to assign them IDs at all (see solution).
Relatively minor, but inline event handling (i.e., using the onclick attribute) is generally regarded as an outdated concept. Instead, use the addEventListener() paradigm to make your code easier to interpret and debug.
The function name F1() isn't particularly descriptive to what the function actually does when called. A future developer maintaining your code would have a less difficult time discerning what this method does if it was named something more descriptive, like validateForm(), or even just validate().
To satisfy your requirements, you might look to write something more like what I've got below. In a nutshell, when the validate() function is run, the following actions are taken:
Instantiates validNumbers, an array of the valid inputs to be validated against he fields.
Instantiates inputFields which evaluates to a NodeList iterable which can be looped.
Instantiates feedbackElement, a reference to your <p> node in the DOM.
Loops all the inputFields, and checks if the value of the current field is in the validNumbers array.
If the validation is successful, the code removes the valid value from the validNumbers array (as it's already been used and can't be used again) and proceed to validate the next field.
If the validation is unsuccessful, the code automatically sets the text of your feedback element to "bad" and breaks out of the validation loop.
Once all three input fields have been validated, the code checks to see if there are any remaining elements of validNumbers left. If there are, not all elements were used and the feedback is once again set to "bad". Otherwise, the validation checks out and the feedback element's contents are set to "good".
function validate() {
var validNumbers = [1, 2, 3];
var inputFields = document.querySelectorAll("input[type='text']");
var feedbackElement = document.querySelector("p#feedBack");
for (field of inputFields) {
var fieldValue = parseInt(field.value);
if (validNumbers.includes(fieldValue)) {
// valid number & not encountered yet
validNumbers.splice(validNumbers.indexOf(fieldValue), 1);
} else {
// invalid number or already used
feedbackElement.innerText = "bad";
break; // break out of `for` loop, as there's already no possible way for the provided numbers to be "good"
}
}
if (validNumbers.length === 0) {
// all numbers were used
feedbackElement.innerText = "good";
} else {
// not all numbers were used
feedbackElement.innerText = "bad";
}
}
document.querySelector("button").addEventListener('click', function() {
validate();
});
<input type=text>
<input type=text>
<input type=text>
<button>check</button>
<p id="feedBack"> </p>
You could try to loop over the input elements and then validate each element, in the loop save the value of the input in a set/array for each input so with this we would have a cache to check the input field values.
function validate() {
let numberEntered = new Set();
let allowedValues = [1, 2, 3];
let inputStatus = false;
let feedBack = document.getElementById("feedBack");
let inputEle = document.querySelectorAll(".number-field");
for (let input of inputEle) {
let value = parseInt(input.value);
if (allowedValues.includes(value) && !numberEntered.has(value)) {
numberEntered.add(value)
} else {
inputStatus = true;
break;
}
}
if (inputStatus) {
feedBack.textContent = "Bad";
} else {
feedBack.textContent = "good";
}
}
<input id="userAnswer1" class="number-field" type=text>
<input id="userAnswer2" class="number-field" type=text>
<input id="userAnswer3" class="number-field" type=text>
<button onclick="validate()">check</button>
<label id="L"> </label>
<p id="feedBack"> </p>

Unusual browser error and advice on a Calculate Sum program for javaScript

So I have a problem for class:
Calculate Sums
Create a function called calculateSums that will accept an array of numbers, and return true if the sum of all the positive numbers is greater than or equal to the sum of the absolute value of all the negative numbers.
Method of completion
You should use a while loop in your solution.
It does not matter if you consider 0 a positive or negative number
because it will not change the sum of either side.
After your loop finishes, you should simply return whether the sum of
positive numbers is greater than or equal to the sum of negative
numbers.
It should be possible to call the function as follows:
calculateSums([-1,2]) returns true
calculateSums([-1,2,-3]) returns false
In your code, call your function several times and display the arrays used and the result on your HTML page.
I am having trouble figuring out how to use a while loop to do this. I have one, but it's not right. I am also trying to display the result using "document.getElementByID("message").innerHTML..." and I am getting an error I don't understand.
Here is my code:
/**
* This function calculates the absolute sum of an array of numbers
* #inputs a - an array of numbers
* #returns compare - a boolean
*/
function calculateSum(a) {
//declare variables and set them equal to 0.
var result = 0;
var possum = 0;
var negsum = 0;
var compare;
while (possum >= negsum) {
for (var i = 0; i < a.length; i++) {
var num = a[i];
result = result + Math.abs(num);
if (num%2 == 0) {
possum += result;
} else {
negsum += result;
}
result = 0;
}
if (negsum > possum) {
compare = false;
break;
} else {
compare = true;
break;
}
}
if (compare == true) {
document.getElementById("message").innerHTML = compare;
} else {
document.getElementById("message").innerHTML = compare;
}
return compare;
}
Here is my HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Calculate Sums</title>
<script src = "assignment3.js"></script>
</head>
<body>
<script>
calculateSum([-1,2,3,-2]);
calculateSum([-3,1,-5,2]);
</script>
<p id = "message"></p>
</body>
</html>
I would love someone to help understand the error and offer suggestions of a better way to do this.
Here is the error in the browser:
Uncaught TypeError: Cannot set property 'innerHTML' of null
at calculateSum (assignment3.js:34)
at calculateSums.html:12
I am still very new to coding, so I am often times finding myself frustrated. I really appreciate the help I've found on this site.
Update: I figured out how to fix the while statement.
while (possum >= negsum || negsum > possum)
I'm not sure this is exactly how my teacher imagined it being done because we also have these acceptance criteria:
Calculate Sums
Your code must include a function called calculateSums that accepts
an array of numbers and includes a return statement
Your function must include a while loop.
Your code must use Math.abs() to get the absoluate value of any
negative numbers.
Your code must NOT include an infinite loop.
Your code must conditionally return true or false.
Your code must include multiple variations of tests to show your
function works.
The primary problem in your code is as follows:
When the browser receives the HTML markup in your code, it is processed from top to bottom.
So based on the coed you have shared the code executes in the following order:
The head section loads first as it is on the top. And consequently, your external script assignment3.js also gets loaded. (now the function in the script is available on the global namespace)
<head>
<meta charset="UTF-8" />
<title>Calculate Sums</title>
<script src = "assignment3.js"></script>
</head>
The browser then moves on to the body
<body>
<script>
calculateSum([-1,2,3,-2]);
calculateSum([-3,1,-5,2]);
</script>
<p id = "message"></p>
</body>
First, your script tag executes
<script>
calculateSum([-1,2,3,-2]);
calculateSum([-3,1,-5,2]);
</script>
This works well until this line in the calculateSum function
document.getElementById("message").innerHTML = compare;
Because, at this point, your browser has not gotten around to rendering the <p> tag (the script comes before and executes before this can happen). So document.getElementById("message") can't find the p tag and ends up returning nothing (null). And when you try to access a property on null, you get this error Cannot set property 'innerHTML' of null.
So to fix that, (and as a best practice in general) it's best to put your scripts at the end of the body tag like this (and not in the head):
<body>
<p id = "message"></p>
<script src = "assignment3.js"></script>
<script>
calculateSum([-1,2,3,-2]);
calculateSum([-3,1,-5,2]);
</script>
</body>
This ensures that your page load is not blocked by scripts and also has the side effect that the DOM will have been rendered and ready when your code executes.
Coming to your assignment problem specifically, you can use a while loop to iterate over the array and solve it in a simple manner.
Some change notes:
Move DOM manipulation out of the calculateSum method. This method now has a single clear purpose of calculating the sum and return either true or false.
Write a new function runTestCases which basically creates an array of arrays for the different tests we want to run and runs the calculateSum method for each. It also updates the DOM to reflect the result as stated in your problem statement.
Used while loop to iterate over the array in calculateSum
/**
* This function calculates the absolute sum of an array of numbers
* #inputs a - an array of numbers
* #returns compare - a boolean
*/
function calculateSum(a) {
//declare variables and set them equal to 0.
var result = 0;
var possum = 0;
var negsum = 0;
var currentIndex = 0;
while (currentIndex < a.length) {
var e = a[currentIndex++];
if (e < 0) {
// Negative number
negsum += Math.abs(e);
} else {
// Positive number
possum += e;
}
}
return possum >= negsum;
}
function runTestCases() {
// Array of test cases. (You can add or remove tests as needed)
var tests = [
[-1, 2],
[-1, 2, -3]
];
// Get reference of the list element to show the results
var ul = document.getElementById("result");
// Iterate over the tests aray and update dom to show each result
for (var i = 0; i < tests.length; i++) {
var test = tests[i]; // Get the current test case
var result = calculateSum(test); // Get the result
// Create a new DOM element to display the result
var li = document.createElement('li');
li.innerHTML = JSON.stringify(test) + " <b> " + result + "</b>";
//Appenf newly created element to the list
ul.append(li);
}
}
runTestCases();
<div>
<ul id="result"></ul>
</div>
Here is an answer that I think covers all the requirements:
function calculateSums(nums) {
var posSum = 0,
negSum = 0,
i = 0,
num;
while (i < nums.length) {
num = nums[i];
if (num < 0) {
negSum += Math.abs(num);
} else {
posSum += num;
}
++i;
}
return posSum >= negSum;
}
Uncaught TypeError: Cannot set property 'innerHTML' of null
at calculateSum (assignment3.js:34)
at calculateSums.html:12
You have to call the calculateSum() function after the p tag. It is trying to assign value before it is declare. Put your script before the tag.

How to modify the algorithm to reduce the delay?

I have a lot of inputs like this where user enters value:
<INPUT TYPE=TEXT NAME="Milk" ONKEYUP="convcase(document.convert.Milk.value)">
<INPUT TYPE=TEXT NAME="Buckwheat" ONKEYUP="convcase(document.convert.Buckwheat.value)">
and a lot of calculation like this:
document.convert.Fe.value = BuckwheatFe * document.convert.Buckwheat.value + PeaFe * document.convert.Pea.value + MilkFe * document.convert.Milk.value + ...
document.convert.Hexadecanoic.value = BuckwheatHexadecanoic * document.convert.Buckwheat.value + PeaHexadecanoic * document.convert.Pea.value + MilkHexadecanoic * document.convert.Milk.value + ...
so the result after calculation shows dynamically and when the program has hundreds of products the delay between input and count is too large. I calculate all products: milk, buckwheat... even if the user does not enter their value.
Could you advise me how to modify the algorithm to reduce the delay?
I would approach it something like the following. The inputs that need to be used in the calculation can be denoted with a class, say "itemValue", and retrieved once then cached. This supposes that they don't change.
The markup can look like:
<form name="convert">
Milk: <input class="itemValue" name="Milk" onkeyup="convcase(this)"><br>
Buckwheat: <input class="itemValue" name="Buckwheat" onkeyup="convcase(this)"><br>
Fe value: <input name="Fe"><br>
Hex value: <input name="Hexadecanoic"><br>
</form>
Things like the Fe and Hexadecanoic values can also be cached. It also helps if the collection of nodes is converted to an array so that built–in array functions can be used. These may be slower than using a for loop, so if they are, convert the reduce call to a loop.
// Helper to convert a NodeList to an array so built-in methods can be used
function toArray(list) {
var i = list.length, arr = [];
while (i--) {
arr[i] = list[i];
}
return arr;
}
The function that does the actual work:
var convcase = (function() {
// Cache stuff in a closure. Note that the names of each factor set
// must match the form control name where the related total is written
var factors = {
Fe: {Buckwheat:0.5, Milk:0.75},
Hexadecanoic: {Buckwheat:0.6, Milk:0.82}
};
var nodes;
return function (el) {
// Only get nodes the first time, assumes they don't change, and convert to Array
// This can be done before validation as it's not dependent on it
nodes = nodes || toArray(el.form.querySelectorAll('input.itemValue'));
// Validate el.value here and deal with invalid values.
// Only proceed if it's a valid value
// For each set of factors, do all the calculations and write totals to form
for (var factor in factors) {
// Get the factor set
var set = factors[factor];
// Write the total to the related form control
el.form[factor].value = nodes.reduce(function(sum, node){
sum += node.value * set[node.name];
return sum;
}, 0);
}
};
}());
I wouldn't do this on keyup, I'd wait for the change or blur events so calculation was only done when there was a good chance the user has finished for the moment, otherwise there may be lots of useless calculations.
Store elements in the array and use looping . Also you can store previous values (old one in some other array ) and before performing calculation compare the old value with the new one . If the value changed then only do it .
I know a standard way to handle this. The point is: you don't want every key stroke to trigger the algorithm. You want to wait for the client to stop typing (for a while); then you trigger the algorithm.
(EDIT: by the way, read the comments below. You might be helped just by changing "onkeyup" by "onchange")
With a clearTimeout/setTimeout combination you can do exactly that. I set the 'typing time' to 400ms, feel free to change this value.
<script>
var timer = null;
var interval = 400; // feel free to change this value
function imputChanged(elm) {
// if any previous key stroke happend less than 400ms ago; clearTimeout will cancel that request.
// only the last request will trigger executeAlgorithm().
clearTimeout(timer);
timer = setTimeout(function() {
executeAlgorithm(elm);
},
interval
);
}
function executeAlgorithm(elm) {
// do what ever you have to do here. As an example, I show the value in a div
document.getElementById('messages').innerHTML = elm.value;
}
</script>
<input onkeyup="imputChanged(this)">
<div id="messages"></div>
(EDIT: it looks like onkeyup works better on IE than onimput; I now set the trigger to onkeyup)

Should all javascript be put inside a load() on body event?

Should I put all this inside a load() body event so that it loads after the page has rendered and the DOM objects have been created? (I did find I had to use .innerhtml rather than .value for it to work.) If so how...
(*I know this is rubbish code, but it is better than my last attempt and worse than my next attempt. Once I get some time to comeback to this I will recreate it using a literal constructor with internal functions. I am not looking to take this Javascript any further in functionality. The back-end php I have will handle security and checks)
<script type="text/javascript">
//To get the price of a product applying discount
function getPrice(location,quantity)
{
//Get the product choice
var e = document.getElementById("productList["+location+"]");
var productSelected = e.options[e.selectedIndex].value;
//TODO: Determine discounts based on product choice
switch(productSelected)
{
case '0':
return 0;
case '1':
return 10;
case '2':
return 15;
}
return null;
}
//To update product only
function updateProduct(location)
{
updateRow(location,document.getElementById("quantity["+location+"]").value);
}
//To update only that row
function updateRow(location,quantity)
{
//Check Quantity is a valid Number and also not a float or negative
if (!isNaN(quantity) && parseFloat(quantity) && isFinite(quantity) && (quantity >= 0)) {
quantity = Math.floor(quantity);
} else {
quantity = 0;
};
//Update the quantity input field to whatever quantity is - Investigate later!
document.getElementById("quantity["+location+"]").value = quantity;
//Store old Price for changing total
var oldTotal = document.getElementById("totalPrice").innerHTML;
var oldLinePrice = document.getElementById("linePrice["+location+"]").innerHTML;
//Calculate and Store Prices to adjust the total
var productPrice = getPrice(location,quantity).toFixed(2);
var newLinePrice = (quantity*productPrice).toFixed(2);
//Apply Updates
document.getElementById("unitPrice["+location+"]").innerHTML = productPrice;
document.getElementById("linePrice["+location+"]").innerHTML = newLinePrice;
document.getElementById("totalPrice").innerHTML = (oldTotal-oldLinePrice+parseFloat(newLinePrice)).toFixed(2);
}
</script>
You only need to put any getElement calls, or calls to functions that get elements, in a load function.
Well, you could, but that then could delay some js code from getting started working on parts of your .html coding which get loaded first. It also makes code be indented more than it needs to be, causing a readability issue.

Value from Math.random is causing problems when I call the function inside itself

This script is giving me problems. I've re written it several times but I'm missing something.
'questions' array refers to my objects. These objects are different questions with 'a' always being the correct answer.
The script works fine up to this point. The idea is to increment 'n' from 0 to scroll through my array, and provide the next question when a correct answer is clicked. 'x' is always the correct answer (it always holds 'a').
So I can increment 'n' just fine on correct guess; however, when I call the function 'a()' again, (after my alert 'hi' part), it is causing problems. I want to increment n, and call a() so I get the next question. Then I want to place the correct guess (x) in a random place (ie position 0, 1 or 2.)
Grateful for any help.
var questions = [q0,q1,q2,q3,q4,q5,q6,q7];
var n = 0;
function a(){
var y;
var z;
var x = Math.floor((Math.random() * 3))
if(x == 0){y = 1; z = 2}else if(x == 1){y = 0; z = 2}else{y = 0; z = 1}
$("#question_holder").text(questions[n].q);
$(".answer_holder").eq(x).text(questions[n].a);
$(".answer_holder").eq(y).text(questions[n].b);
$(".answer_holder").eq(z).text(questions[n].c);
$(document).ready(function(){
$(".answer_holder").eq(x).click(function(){
alert("hi");
n++;
a();
/*this area needs to get the next question by incrementing
n, then generate a different value to x, to place it
in a different position. which it does. however,
in subsequent questions, you can click the wrong answer as if
it's correct, ie not 'a' or 'x'.*/
});
});
};
Your logic is a bit strange here.. what you are trying to do is register a new click event every time a() runs. I think you want to register one click event for all answer_holder elements, and in the event handler check which element this is and handle it accordingly
Notice the following:
$(document).ready(function(){ - the function defined in this handler is supposed to run once your page is loaded.. I don't think you want to use it inside a(). It is usually only used in global scope (outside all functions)
$(".answer_holder").eq(x).click(function(){ - this event handler registers your function depending on the value of x. I suggest you register an event handler for all $(".answer_holder"), without depending on x (in the global scope). And inside that event handler (in the function), you check which element triggered the event (using $(this) - it returns the element that was clicked)
You have the $(document).ready() in the wrong place. Try something like this (caveat: this is completely untested):
function setupQuestion(n) {
var x,y,z;
x = Math.floor((Math.random() * 3))
if(x == 0){y = 1; z = 2}else if(x == 1){y = 0; z = 2}else{y = 0; z = 1}
$("#question_holder").text(questions[n].q);
$(".answer_holder").eq(x).text(questions[n].a).data('answer', 'a');
$(".answer_holder").eq(y).text(questions[n].b).data('answer', 'b');
$(".answer_holder").eq(z).text(questions[n].c).data('answer', 'c');
}
$(document).ready(function() {
var n = 0;
$('.answer_holder').click(function() {
if ($(this).data('answer') === 'a') { // Or whatever is the correct answer
n++;
if (n < questions.length) {
setupQuestion(n);
} else {
// Do something else, the test is finished
}
}
return false;
});
setupQuestion(n);
});
Note that I am not comparing on the text() function but rather the data() function. This is because the text as displayed to the user might be decorated in some way, making comparisons difficult. Simply using the answer index 'a' 'b' or 'c' is clearer.

Categories