How to modify the algorithm to reduce the delay? - javascript

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)

Related

how can i make this script show the value of the var "per" based on the new value of the var "a" that raise on every click

I want to be able to display a 'clicker' variable, storing the number of clicks on a certain element, and then display it when i click on another element.
per = a * 6.6666 so I tried again and it wont work the result stay 0 but if I change the value of a to 1 it shows 6.6666 so the calculation is working fine but it is not taking the value of a based on how many times I click it just take the value I gave in the script ...
var a = 0 ;
const six = 6.6666;
var per = a * six;
$(".mychoice").click(function () {
(a++);
});
$(".show").click(function () {
$("#ss").text( per );
});
Calculations are done when you tell them to be done.
If you change the value of a in a click event, then that value doesn't travel back through time so that a is the new value back when you read it and used it to multiply six.
If you want to redo that calculation when something is clicked then you need to write that expression in that click event handler function.
var per = a * six;
The calculation is executed and stored in the per variable as soon as the line is executed, not when you reference the per variable.
If you want to calculate the value at a later time, use an (arrow) function instead.
// define `per` as arrow function with 0 arguments
var per = () => a * six;
Then you evoke the function when you need the result:
$(".show").click(function () {
$("#ss").text( per() );
});
In this scenario doing the calculation directly, without defining a function, might be cleaner.
$(".show").click(function () {
$("#ss").text(a * six);
});
Keep in mind that a and per are two distinct locations in memory, therefore, you have to update the per variable along with the a variable.
var a = 0;
const six = 6.6666;
var per = a * six;
$(".mychoice").click(function () {
(a++);
// memory location labelled "a" has changed
// update memory location labelled "per" accordingly
per = a * six;
});
$(".show").click(function () {
$("#ss").text(per);
});

Property returns undefined even though the value exists...?

I have this object resources:
var resources = { //Handles resources of all kinds.
number: 100,
money: 23000000,
science: 1000,
popularity: {
amount: 0,
upgProd: 0 //Amount produced from upgrades.
}
};
This looks like a normal object.
However, I'm trying to display a certain quantity popularity. Every time I try to display it, I get a NaN instead of a number.
I try to return console.log(resources.popularity.upgProd); but I still end up getting an undefined. I have no clue why since I define the variable but I still get undefined...
No errors in the IDE or in the console, just undefined when I console.log().
EDIT: Here is some surrounding context... this is my update function, that updates every 1/7 second. ALSO, the first value of resources.popularity.upgProd is 0, then the next become NaN.
function update() {
buffer++;
if (buffer == 35) {
checkVisibilityOnBuildings();
checkVisiblityOnUpgrades();
checkVisibilityOnResources();
buffer = 0;
} // Every 5 seconds (35 ticks) visibility will be checked and updated.
/* Number increasing. A bit tedious but no other way to do it yet. */
resources.number +=
((BUILDINGS[0].numProdBase * BUILDINGS[0].count) + //Now it's easy! Just do UPGRADES[n+1] for the next building.
(BUILDINGS[1].numProdBase * BUILDINGS[1].count) +
(BUILDINGS[2].numProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].numProdBase * BUILDINGS[3].count));
//Science gained per tick. Var used to make the "scienceProductionTotalDisp" work properly
var scienceTotalPerTick =
(BUILDINGS[2].sciProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].sciProdBase * BUILDINGS[3].count);
resources.science += scienceTotalPerTick;
//Display vars for html so that rounding errors don't happen.
var numDisp = Math.floor(resources.number);
var sciDisp = Math.floor(resources.science * 100) / 100;
var popDisp = Math.floor(resources.popularity.amount * 100) / 100;
console.log(Number(resources.popularity.upgProd));
var moneyTotalPerTick = Math.pow(resources.number, (1/(player.moneyRatio))) + 1; //Cash flow per 143ms (7n for total / sec ish)
var popularityTotalPerTick = (Number(resources.popularity.upgProd)) + 0;
resources.popularity += popularityTotalPerTick;
console.log(resources.popularity.upgProd);
resources.money += moneyTotalPerTick;
getId('moneyProductionTotalDisp').innerHTML = numFormat(Math.floor(moneyTotalPerTick * 7));
getId('moneyDisp').innerHTML = numFormat(Math.round(resources.money * 100) / 100);
getId('numberDisp').innerHTML = numFormat(numDisp);
getId('scienceDisp').innerHTML = numFormat(sciDisp);
getId('popularityDisp').innerHTML = numFormat(popDisp);
getId('scienceProductionTotalDisp').innerHTML =
numFormat(Math.floor(scienceTotalPerTick * 700) / 100);
getId('popularityProductionTotalDisp').innerHTML =
numFormat(Math.floor(popularityTotalPerTick * 700) / 100);
Thank you!
Here is your problem:
resources.popularity += popularityTotalPerTick;
popularity is an object, and that doesn't do what you want.
Since you overwrite it with the result of an object added by a value, you assign it s string [object Object]9 where the last digit is whatever was in popularityTotalPerTick.
You get NaN (Not a number) since you are using Number(x)in console.log(Number(resources.popularity.upgProd));. Why are you doing that?
Does getId do a lookup of the element in the dom every time your function is called? Have the object changed or are you querying the DOM for the same element 7 times per second?
Some thoughts about the other tings in your code:
resources.number +=
((BUILDINGS[0].numProdBase * BUILDINGS[0].count) +
(BUILDINGS[1].numProdBase * BUILDINGS[1].count) +
(BUILDINGS[2].numProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].numProdBase * BUILDINGS[3].count));
I'm assuming that BUILDINGS is an array with all the buildings, and that you want to calculate the number of all buildings in the array. There is a function for that: reduce that takes two parameters: a function and the start value:
resources.number += // do you really want += here and not just = ?
BUILDINGS.reduce( (sum, item) => sum + (item.numProdBase * item.count), 0 );
If your aren't familiar with arrow-functions it could be replaced with:
function (sum, item) { return sum + (item.numProdBase * item.count) }
var scienceTotalPerTick =
(BUILDINGS[2].sciProdBase * BUILDINGS[2].count) +
(BUILDINGS[3].sciProdBase * BUILDINGS[3].count);
I'm not sure why you are only doing it for two buildings, and not for all, but you could use reduce here too, with slice
var scienceTotalPerTick =
BUILDINGS.slice(2,4).reduce( (sum, item) => sum + (item.sciProdBase * item.count), 0);
Notice that the parameters to slice is begin to end (end not included), therefor 2,4 gives you element 2 and 3.
With this part of the code...
getId('moneyProductionTotalDisp').innerHTML = numFormat(Math.floor(moneyTotalPerTick * 7));
getId('moneyDisp').innerHTML = numFormat(Math.round(resources.money * 100) / 100);
getId('numberDisp').innerHTML = numFormat(numDisp);
getId('scienceDisp').innerHTML = numFormat(sciDisp);
getId('popularityDisp').innerHTML = numFormat(popDisp);
I assume that getId is a function that fetches the element via document.getElementById or document.querySelector, and that you do this for every frame, and you get the same element every time. Now imagine that the element is a box, and that I demand that you get it from the warehouse that is on the other side of the world. When you deliver the box, I open the box, replace the contents and send it back to the warehouse on the other side of the world. When you come back, I demand that you get the same box again, and you now have to travel to the other side of the world once again... and when you come back the story repeats...
My point here is that it is very wasteful to travel to the other side of the world each time to get the same box, or to get the same element from the DOM at each update. You could cache the elements by looking them up ONE time before you start the update, and you can put the result in an object:
function getElementsToUpdate() {
return {
moneyProductionTotalDisp: getId('moneyProductionTotalDisp'),
moneyDisp: getId('moneyDisp'),
// and so on...
}
}
If you name the property in the object the same as the id, you can put all the names in an array, and then reduce it to an object. This saves you some typing, and hard to find bugs because of a misspelled name:
function getElementsToUpdate() {
return [
'moneyProductionTotalDisp',
'moneyDisp',
'numberDisp',
'scienceDisp',
'popularityDisp',
// and so on....
].reduce(
(out, id) => { out[id] = getId(id); return out; }, {}
)
}
NOTE: This function should be run ONE time at startup, not for every update of the frame. It returns an object with the elements.
Somewhere in your code I assume that you use setInterval to call your update function. Since setInteval can take extra parameters that will be given to the called function, you can do something like this:
var timerHandle = setInterval( update, 1000/7, getElementsToUpdate() );
where update is your function, 1000/7 gives you the interval for 7 times a second, and getElementsToUpdate is the function that makes the time-expensive call to get the elements from the DOM one time.
You need to change the update function to take a parameter (the name is not important, but should be short and descriptive, so I use elem). This is the object that getElementsToUpdate() have returned with all the html-elements.
function update(elem) {
// ... your code ....
// Old code, that makes an expensive lookup into the DOM, and start a HTML parser.
// getId('numberDisp').innerHTML = numFormat(numDisp);
// New code, that gets the pre-looked up element and set the text.
elem.numberDisp.textContent = numFormat(numDisp);
}
I'm not a fan of using .innerHTML when it isn't html that is inserted. Always use .textContent instead, if is isn't html. In this case it would be better if you use the output-element, and set the .value property.
try using console.log(resources["popularity"]["upgProd"])

My if statement gives me the wrong output

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>

I am trying to stop my function from displaying the same object twice when clicking a button

I have for quite some time now been trying to figure out how I can stop my code to print the same quote twice.
Also, when every single object in the array has been printed out, I'd like for it to reset somehow. So that you can browse through the quotes once you've gone through all of them.
This is the essential parts of my code:
document.getElementById('loadQuote').addEventListener("click", printQuote, false);
The printQuote function simply contains information that's accessing information from my array:
var randomObjectNumber = getRandomQuote();
var html = "<p class='quote'>"
+ quotes[randomObjectNumber].quote +
"</p>";
document.getElementById('quote-box').innerHTML = html;
One random object is displayed each time you click the eventListener:
function getRandomQuote () {
var randomObjectNumber = Math.floor(Math.random() * quotes.length );
return randomObjectNumber;
}
I have some ideas on how to do this and I have tried them but without success. I tried giving each object a boolean property but I can't really seem to assign each property a boolean value without messing the printQuote function up.
I also tried assigning the object displayed to a different array but the same problem occurred there.
I feel like there is some concepts around the eventListener that I don't fully understand, because every time I try to manipulate a displayed object I just end up changing every single object.
This is what a typical object in the array looks like by the way:
{quote : "Darkness is merely the absence of light"}
(I also have other properties assigned to the object but i feel like presenting them would be redundant)
If someone could explain, or give me a hint, on how to solve this problem I've been struggling with for some time.
Some hints would be greatly appreciated!
Have a nice day.
Sebastian.
EDIT: All code: https://jsfiddle.net/fusqb7hz/
Basically what you need:
Create a separate array that will store all quotes that you've already used.
Remove quote from initial array.
Check if you still have quotes in initial array, if not, get them back from backup array.
The problem is that you call addEventListener twice:
//Let's developers create multiple eventListeners without being redundant.
function onClicking (printFunction) {
document.getElementById('loadQuote').addEventListener("click", printFunction, false);
}
onClicking(printColor);
onClicking(printQuote);
by calling onClicking twice you make the click happen twice, so addEventListener is added twice, meaning one click counts as two.
Change the above code for this:
//Let's developers create multiple eventListeners without being redundant.
document.getElementById('loadQuote').addEventListener("click", function(){
printColor();
printQuote();
});
Here is the jsfiddle:
https://jsfiddle.net/fusqb7hz/3/
I think the easiest approach is to shuffle your quote array and then go through them one by one. This gives you the next "random" as yet unseen quote. The only part I'm not keen on is this shuffler (a derivation of Fisher Yates) modifies the original quote array. You might not care about that though.
// --------------------------------
// A bunch of quotes
// --------------------------------
var quotes = [];
quotes.push({quote : "Darkness is merely the absence of light"});
quotes.push({quote : "quote 2"});
quotes.push({quote : "quote 3"});
quotes.push({quote : "quote 4"});
quotes.push({quote : "quote 5"});
// --------------------------------
// --------------------------------
// Your favorite array shuffle utility
// --------------------------------
var shuffle = function(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
};
// --------------------------------
// --------------------------------
// construct a function to get a random unseen quote until
// all quotes have been seen. Then reset...
// --------------------------------
var getQuote = (function(quotes, shuffle){
var current = 0;
var get = function(){
if ( !quotes || !quotes.length ) { return ""; }
if ( current >= quotes.length ){ current = 0; }
if ( current === 0 ){
console.log("randomizing quotes...");
shuffle(quotes);
}
return quotes[current++].quote;
};
return get;
})(quotes, shuffle);
// --------------------------------
var printQuote = function(){
document.getElementById('quote').innerText = getQuote();
};
document.getElementById('loadQuote').addEventListener("click", printQuote, false);
<div id="quote"></div>
<button id="loadQuote">get quote</button>

Javascript Math Functions

How can I use these JavaScript math functions ?
For example, I want to compute the square of all <input> values in a form, without submiting the form.
Can you give a little example? Thank you.
JQuery doesn't need to support math functions as it is an addon library for Javascript, you can still use Javascript in your JQuery code, so you can still use all the native math functions.
Examples:
Addition
var x = 1;
var y = 2;
var lol = x+y;
alert(lol);
Subtraction
var x = 10;
var y = 1;
var lol = x-y;
alert(lol);
Edit: Now we understand your question a little better...
<input type="text" id="field1" value="16" />
<input type="text" id="field2" value="25" />
<input type="text" id="field3" value="36" />
var field1Value = document.getElementById("field1").value;
var field2Value = document.getElementById("field2").value;
var field3Value = document.getElementById("field3").value;
alert(Math.sqrt(field1Value ));
alert(Math.PI * field2Value);
alert(Math.sin(field3Value));
You can act on each individual input using an each()(docs) loop.
Click here to test a working example. (jsFiddle)
$('a.square').click(function() {
$('#myform :text').each(function() {
this.value *= this.value;
});
});
$('a.square_root').click(function() {
$('#myform :text').each(function() {
this.value = Math.sqrt(this.value);
});
});
When either link is clicked, it finds all the text inputs in myform and iterates over them.
Inside the each function, this refers to the current input element.
JavaScript is the programming language, not jQuery, which is a library for web application programming written in JavaScript. To effectively use jQuery, you need to know JavaScript.
It is, however, possible to use jQuery's functionality to easily work with multiple textboxes at once:
// Set each single-line textbox's value to the square
// of its numeric value, if its value is in fact a number.
$('input:text').each(function() {
var num = +this.value;
if(!isNaN(num)) {
this.value = num * num; // or Math.pow(num, 2)
}
});
It would be quite useful if jQuery had a reduce() function.
When dealing with lists of data, most functional languages, and indeed most traditional languages these days, have methods that perform a repetitive function over the entire list, taking each element in turn and applying a function to it.
The simplest of these is map, which jQuery implements for you. This takes a list and applies a function to each element and returns the list of results, one result per entry in the list. eg. [1,2,3] -> (map x2) -> [2,4,6].
Sometimes you want a total or collective result from a list, rather than a list of individual mappings. This is where the reduce (or fold) operation comes in. Unfortunately jQuery does not have this method available as standard, so below is a plugin for it. A reduce function takes an accumulator value and the value of the current element, and returns the modified accumulator, which will be passed on to the next call. eg. [1,2,3,4] -> (reduce + [initial:0]) -> 10 = ( ( ( (0 + 1) + 2 ) + 3 ) + 4 ) or ([1,2,3,4] -> (reduce * [initial:1]) -> 24 = ( ( ( (1 * 1) * 2 ) * 3 ) * 4 ).
(function($) {
$.reduce = function(arr, callback, initial) {
var accumulator = initial || 0;
$.each(arr, function(index, value) {
accumulator = callback(accumulator, value, index);
});
return accumulator;
}
})(jQuery);
Then you can use it like this to get a sum of squares:
var answer = $.reduce($('input:text'), function(acc, elem) {
var cVal = $(elem).val();
return acc + cVal * cVal;
}, 0);
i was looking for a solution too , and i saw a lot of questions here that doesn't work (even this one) in case someone wondering like me , here is my working solutiuon :
$("#apport").keyup(
function(){
var apport = parseFloat($("#apport").val());
var montant = parseFloat($("#montant-financer").val());
var moinmontant = parseFloat(montant) - parseFloat(apport);
$("#montant-financer").val(moinmontant);
}
);
All the id's selector are input
Use the jquery map function to create an array
$('input:text').map(function() {
return this.value * this.value; // math calculation goes here
}).get();
See a live example
Looking at the initial question that was posted, it clearly states compute the square of all values in a form, without submiting the form.
i think keyup would be the best solution.
$("input").keyup(function () {
var value = $(this).val();
var x=value*value;
$("p").text(x);
}).keyup();
Click here to check the working example.
http://jsfiddle.net/informativejavascript/Sfdsj/3/
For more details visit http://informativejavascript.blogspot.nl/

Categories