JavaScript closure, just not getting it - javascript

I'm trying to add inputs iteratively, and be able to run a calculation independently on each, but can not seem to apply the closure principles. The calculation function is only working on the last item added. I've tried using a for loop within as well as around the main function (addIt()) but it only seems to make things worse...
Here's the basic html:
<button class="btn btn-default btn-block" onClick="addIt('Item'+count)">Add One</button>
<form>
<div id="itemForm"></div>
<form>
And here is my overly complex and inelegant js (by the way, I'm open to better ways of doing this, so please don't hesitate to jump all over this):
count = 0;
addIt = function(p) {
count++;
var itFrm = document.getElementById("itemForm");
var itDiv = document.createElement("div");
var children = itFrm.children.length + 1
itDiv.setAttribute("id", "itemDiv")
itDiv.appendChild(document.createTextNode(p));
itFrm.appendChild(itDiv);
var remove = document.createElement("a");
var linkText = document.createTextNode("Remove It");
remove.setAttribute("href", "#");
remove.setAttribute("onClick", "removeIt()");
remove.appendChild(linkText);
var brk = document.createElement("br");
var num = document.createElement("input");
num.setAttribute("id", "numInput"+count);
num.setAttribute("type", "number");
num.oninput = function () {
var numInput = document.getElementById('numInput'+count).value ;
var divisor = 10;
var result = document.getElementById('result'+count);
var myResult = (Number(numInput) / Number(divisor));
result.value = myResult;
};
num.setAttribute("placeholder", "Set number...");
var clc = document.createElement("input");
clc.setAttribute("id", "result"+count);
clc.setAttribute("readonly", "readonly");
clc.setAttribute("placeholder", "After Calculation...");
var hr = document.createElement("hr");
itDiv.appendChild(remove);
itDiv.appendChild(num);
itDiv.insertBefore(brk, num);
itDiv.appendChild(clc);
itDiv.appendChild(hr);
};
function removeIt(elem) {
var elem = document.getElementById('itemDiv');
elem.parentNode.removeChild(elem);
return false;
};
I tried to setup a jsfiddle here but for some reason the removeIt function doesn't work there, although it's working locally for me, but only removes the oldest iteration. Any thoughts on how I've botched that are welcomed and appreciated as well.

var countString = count.toString();
num.oninput = function() {
var numInput = document.getElementById('numInput' + countString).value;
var divisor = 10;
var result = document.getElementById('result' + countString);
var myResult = (Number(numInput) / Number(divisor));
result.value = myResult; };
It was a scoping issue with count. Its basically a global variable so the closure will look for it. Use a local variable that gets re declared on each button press to fix it.

Related

Updating value in for loop / Reseting a for loop?

I'm working on my first school project so I don't have much experience in doing such web applications, that's why I decided to ask here.
How can I update the value in the for loop syntax or reset it entirely, so it iterates again, like I just reloaded it? I have another function that I decided not to show, simply because it would be useless to. What it does in the end is increments the taskCount.length by one. This part technically works but problem is, the function I'm going to show you now, once iterated, will always keep the default taskCount.length value, once the page is loaded, it never changes there. Is there any way I can update it?
Here's an example: The function above makes taskCount.length = '5' but when the page started it was taskCount.length = 4, and when I do alert(taskCount.length) from the console, I get 5. But the for loop doesn't want to change.
for (var i = 0; i < taskCount.length; i++) {
document.getElementsByClassName('task')[i].addEventListener('click', ((j) => {
return function() {
var shadow = document.createElement('div');
// Styling
var changingWindow = document.createElement('div');
// Styling
var changingTitle = document.createElement('p');
// Styling
var changingText = document.createElement('p');
// Styling
var changingTitleNode = document.createTextNode('Промяна');
var changingTextNode = document.createTextNode('Моля, изберете действие.');
var deleteTask = document.createElement('button');
var goUp = document.createElement('button');
var goDown = document.createElement('button');
var unchange = document.createElement('button');
// Styling
var deleteElementNode = document.createTextNode('Премахни задачата');
var goUpNode = document.createTextNode('Премести нагоре');
var goDownNode = document.createTextNode('Премести надолу');
var unchangeNode = document.createTextNode('Отказ');
var justBreak = document.createElement('br');
var justBreakAgain = document.createElement('br');
var justBreakOneMoreTime = document.createElement('br');
body.appendChild(shadow);
shadow.appendChild(changingWindow);
changingWindow.appendChild(changingTitle);
changingTitle.appendChild(changingTitleNode);
changingWindow.appendChild(changingText);
changingText.appendChild(changingTextNode);
changingWindow.appendChild(deleteTask);
deleteTask.appendChild(deleteElementNode);
deleteTask.onclick = function() {
document.getElementsByClassName('task')[j].parentNode.removeChild(document.getElementsByClassName('task')[j]);
shadow.parentNode.removeChild(shadow);
localStorage.setItem("listContent", document.getElementById('list').innerHTML);
}
changingWindow.appendChild(justBreak);
changingWindow.appendChild(goUp);
goUp.appendChild(goUpNode);
goUp.onclick = function() {
if (j !== 0) {
var saveThisTaskValue = document.getElementsByClassName('task')[j].innerHTML;
var savePreviousTaskValue = document.getElementsByClassName('task')[j - 1].innerHTML;
document.getElementsByClassName('task')[j].innerHTML = savePreviousTaskValue;
document.getElementsByClassName('task')[j - 1].innerHTML = saveThisTaskValue;
}
shadow.parentNode.removeChild(shadow);
localStorage.setItem("listContent", document.getElementById('list').innerHTML);
}
changingWindow.appendChild(justBreakAgain);
changingWindow.appendChild(goDown);
goDown.appendChild(goDownNode);
goDown.onclick = function() {
if (j !== document.getElementsByClassName('task').length - 1) {
var saveThisTaskValue = document.getElementsByClassName('task')[j].innerHTML;
var saveNextTaskValue = document.getElementsByClassName('task')[j + 1].innerHTML;
document.getElementsByClassName('task')[j].innerHTML = saveNextTaskValue;
document.getElementsByClassName('task')[j + 1].innerHTML = saveThisTaskValue;
}
shadow.parentNode.removeChild(shadow);
localStorage.setItem("listContent", document.getElementById('list').innerHTML);
}
changingWindow.appendChild(justBreakOneMoreTime);
changingWindow.appendChild(unchange);
unchange.appendChild(unchangeNode);
unchange.onclick = function() {
shadow.parentNode.removeChild(shadow);
}
}
})(i))
}
As a matter of the page reloading, you can always save the value as a cookie and reuse it again and again. You can update it whenever you want.
I don't fully understand you question, but maybe some recursion is what you need. Something along the lines of:
loop(5);
function loop(xTimes) {
for (var i = 0; i < xTimes; i++) {
if (newXTimes !== xTimes) {
loop(newXtimes);
break;
}
}
}
Maybe set newxTimes as a global variable that can be accessed inside loop.
In case someone "from the future" reads this question and it doesn't have any answers, I came up with the solution to reload the page everytime you change the value. Still, I'd like to do it without reloading.

How to exchange counter after delete field using plain javascript

I'm new in javascript. i have a JS function that add and remove input fields. its working fine with my JS function. But I want when delete a field its Id looks like:
I have
no. 1
no. 2
no. 3
After Delete 2:
no. 1
no. 2
already i got this answer:
Reset JavaScript Counter after Deleting a field
But i want it with plain javascript. Can anyone help?
<script>
var count = 1;
function add_new(){
count++;
var div1 = document.createElement('div');
div1.id = count;
var delLink = '<button type="button" onclick="deleteLink('+count+')" class="btn btn-primary">Delete</button>';
div1.innerHTML = document.getElementById('add_link1').innerHTML+delLink;
document.getElementById('add_link').appendChild(div1);
document.getElementById("input_link1").id = count;
document.getElementById("input_link2").id = count;
document.getElementById("input_link3").id = count;
}
function deleteLink(eleId){
var ele = document.getElementById(eleId);
var par = document.getElementById('add_link');
par.removeChild(ele);
}
</script>
After deleting an element call the following function to reset Id of existing elements and also reduce the count.
function reset_counter(deletedCount) {
for (var impactedElementId = deletedCount + 1; impactedElementId < count; impactedElementId++) {
var currentElement = document.getElementById(impactedElementId);
currentElement.id = impactedElementId - 1;
var button = currentElement.firstChild;
button.innerHTML = 'Delete ' + currentElement.id;
button.setAttribute('onclick', 'deleteLink(' + currentElement.id + ')');
}
count--;
}
The full code is available here: AddDeleteElements Sample Code

Form value always read as undefined in Javascript

I'm a new self taught programmer working on my first homework assignment, so I apologize if my naming convention is off. This is the most bizarre thing. No matter how I request the input value, (hoping to pull a number) it always reads as undefined.
Everything works in my javascript function except pulling the input value. I have used forms in the past, and the variables appear to be referencing it fine; I have tried both document.formName.inputName.value, as well as document.getElementById ('input-id').value and it returns undefined. I have renamed my form and variables so many times to see if that was the issue and stI'll nothing. I have tried both input type text and number, and stI'll undefined.
Am I missing something due to how new I am? Please help. Links to github and jsfiddle below.
https://github.com/MissElle/calculator?files=1
https://jsfiddle.net/MissElle/qf7xL8gj/
var dataInput = document.compute.calculate.value;
var element = Number(dataInput);
var numCount = document.getElementById('count');
var numSum = document.getElementById('sum');
var numMean = document.getElementById('mean');
var subCount = [];
var subSum = 0;
var starColors = ['#51fffc', '#ffff96', '#96ffc7', '#f8d8ff', '#d2bfff', '#ffbfbf', '#ffd299', '#ffffff', '#000000'];
function calcData(element) {
if(typeof element === 'number') {
console.log(element);
subCount.push(element);
var starDiv = document.createElement('div');
starDiv.className = 'star';
var starHolder = document.getElementById('star-holder');
starHolder.appendChild(starDiv);
starDiv.style.background = 'radial-gradient(circle, ' + starColors[Math.floor(Math.random() * starColors.length)] + ', transparent, transparent)';
numCount.innerHTML = subCount.length;
for(var i in subCount) {
subSum += subCount[i];
numSum.innerHTML = subSum;
var subMean = subSum/subCount.length;
numMean.innerHTML = subMean;
}
}else {
numCount.innerHTML = 'Not a Number';
console.log(element);
}
subSum = 0;
event.preventDefault();
}
function clearData() {
subCount = [];
subSum = 0;
subMean = 0;
numSum.innerHTML = '';
numMean.innerHTML = '';
numCount.innerHTML = '';
var starHolder = document.getElementById('star-holder');
var starDiv = starHolder.getElementsByClassName('star');
while(starDiv.length > 0) {
starHolder.removeChild(starDiv[0]);
}
}
<form name="compute" onsubmit="calcData()" onReset="clearData()">
<p class="bold">Please enter a number</p>
<input type="number" name="calculate" id="calculation" step="any"><br>
<input type="submit" name="submit" value="star">
<input type="reset" value="nostar" name="clearForm">
<div class="row">
<div class="typevalue"><h4>Count:</h4><h4>Sum:</h4><h4>Mean:</h4></div>
<div class="numbervalue"><p id="count"></p><p id="sum"></p><p id="mean"></p></div>
</div>
</form>
Move your variable declarations inside your function like this:
function calcData(element) {
var dataInput = document.compute.calculate.value;
var element = Number(dataInput);
var numCount = document.getElementById('count');
var numSum = document.getElementById('sum');
var numMean = document.getElementById('mean');
var subCount = [];
var subSum = 0;
var starColors = ['#51fffc', '#ffff96', '#96ffc7', '#f8d8ff', '#d2bfff',
'#ffbfbf', '#ffd299', '#ffffff', '#000000'];
...
If you declare your dataInput outside the function you get no value because that JS code is run after the page loads (before your user types any number in your function).
You have to declare it inside your function that way you get the value of your input when the user clicks on the button.

Multiplying Variables Not Alerting

I have a script which calls variable values from input fields and multiplies them,
At the minute my function isnt executing, Im getting no alert neither, I think this is because of my if statement, can anybody see whats going wrong?
function Calculate() {
var ContentMinutes = document.getElementById ("ContentMinutes").value;
var ContentMinutesSelect = document.getElementById('ContentMinutesDD')
.options[document.getElementById('ContentMinutesDD').selectedIndex].value
if (ContentMinutesSelect == 0.0166)
{
var RenderingHours = 10;
var VideoHours = 5;
var VideoSeconds = 1;
document.getElementById("RenderHours").innerHTML=RenderingHours;
document.getElementById("VideoHours").innerHTML=VideoHours;
document.getElementById("VideoSeconds").innerHTML=VideoSeconds;
}
else if (ContentMinutesSelect == 0.0003)
{
var RenderingHours = 1540;
var VideoHours = 54;
var VideoSeconds = 1;
document.getElementById("RenderHours").innerHTML=RenderingHours;
document.getElementById("VideoHours").innerHTML=VideoHours;
document.getElementById("VideoSeconds").innerHTML=VideoSeconds;
}
else
{
var RenderingHours = 6410;
var VideoHours = 345;
var VideoSeconds = 124;
document.getElementById("RenderHours").innerHTML=RenderingHours;
document.getElementById("VideoHours").innerHTML=VideoHours;
document.getElementById("VideoSeconds").innerHTML=VideoSeconds;
}
var NoOfFrames = document.getElementById ("NoOfFrames").value;
//var EstimatedCoreHours = document.getElementById ("EstimatedCoreHours").value;
var ServiceLevel = document.getElementById('SerivceLevelDD')
.options[document.getElementById('SerivceLevelDD').selectedIndex].value;
var RenderHours = 1;
var CoresInTest = document.getElementById ("CoresInTest").value;
var EstimatedCoreHours = GetNumeric(NoOfFrames)
* GetNumeric(RenderingHours)
* GetNumeric(CoresInTest);
var EstimatedTotal = GetNumeric(ServiceLevel)
* GetNumeric(EstimatedCoreHours);
alert('Estimated Cost = '
+EstimatedTotal.toFixed(2)
+ 'Estimated Core Hours = '
+EstimatedCoreHours);
document.getElementById("EstimatedCoreHours").innerHTML =
EstimatedCoreHours.toFixed(2);
document.getElementById("EstimatedTotal").innerHTML =
EstimatedTotal.toFixed(2);
document.getElementById("EstimatedCoreHours").style.backgroundColor="yellow";
document.getElementById("EstimatedTotal").style.backgroundColor="yellow";
}
function GetNumeric(val) {
if (isNaN(parseFloat(val))) {
return 0;
}
return parseFloat(val);
}
if (ContentMinutesSelect == 0.0166) i think when you do .value you will get string result.
So your comparision should be
if (ContentMinutesSelect == "0.0166")
Your code will display no alert if any line before it results in an error , like if there isn't an element with the id 'ContentMinutes' in your document . The best way to debug would be to use something like firebug , or you could always put in a bunch of alerts and figure out what goes wrong.

Why is this text effect not working?

I am trying to modify the script from here to work on more then one span.
I have tried this, but it seems to overwrite both spans with the same text.
<html>
<head>
<script>
var got;
var chars;
function change(decSpan,encSpan)
{
var randstring = "";
var rslength = chars.length - got.length;
var decrypted = document.getElementById(decSpan);
var encrypted = document.getElementById(encSpan);
for(var x=0;x<rslength;x++)
{
i = Math.floor(Math.random() * chars.length);
randstring += chars.charAt(i);
}
if(randstring.charAt(0) == chars.charAt(got.length))
{
got += randstring.charAt(0);
decrypted.innerHTML = got;
}
else
{
encrypted.innerHTML = randstring;
}
if(chars.length > got.length)
{
setTimeout("change('"+decSpan+"','"+encSpan+"')", 10);
}
else
{
encrypted.innerHTML = "";
}
}
function startdecrypt()
{
var decodeSpans = ["decoded","decoded2"];
var encodeSpans = ["encoded","encoded2"];
for(var z in decodeSpans)
{
decSpan = decodeSpans[z];
encSpan = encodeSpans[z];
var decrypted = document.getElementById(decSpan);
var encrypted = document.getElementById(encSpan);
chars = decrypted.innerHTML;
decrypted.innerHTML = "";
got = "";
setTimeout("change('"+decSpan+"','"+encSpan+"')", 10);
}
}
</script>
</head>
<body bgcolor="#FFFFFF" text="#000000">
<input type="button" value="go" onClick="javascript:startdecrypt()"><br>
<span id="decoded">Test1</span><span id="encoded"></span><br>
<span id="decoded2">Test2</span><span id="encoded2"></span>
</body>
</html>
The problem is that that script uses global javascript variables: chars and got are set in startdecrypt and used later in change function. Thus, the next iteration of loop overrides previously set values.
The best solution is probably to include them in js call of change function, like you do with ids.
Also, make sure to declare all js variables local to avoid such side-effects: var got = "".
I'll admit not to have gone through the whole of your code, but it seems you made an unintended Javascript closure, like in this example:
Javascript closures - variable scope question
Or, as Nikita put it, global variables, which, when refered to in the change() function make a closure. Note that the var inside the for does not change anything. Javascript does not have block scope.
Note that using var inside the loop or declaring var x at the top of the function has the same effect. You might want to consider using let instead:
Let statemennt from Mozilla Developper
Closures are hard to understand, especially unintended ones. Ender's answer on the link above has helpful links about how they work.
If I understand correctly, this should do what you're looking for:
var decodeSpans = ["decoded", "decoded2"];
var encodeSpans = ["encoded","encoded2"];
function encoder(decSpan, encSpan){
this.encSpan = encSpan;
this.decSpan = decSpan;
var got = "";
var chars = decSpan.innerHTML;
decSpan.innerHTML = "";
return (function(){
var randstring = "";
var rslength = chars.length - got.length;
for(var x=0;x<rslength;x++){
i = Math.floor(Math.random() * chars.length);
randstring += chars.charAt(i);
}
if(randstring.charAt(0) == chars.charAt(got.length)){
got += randstring.charAt(0);
decSpan.innerHTML = got;
}else{
encSpan.innerHTML = randstring;
}
if(chars.length > got.length){
decSpan.innerHTML = chars;
encSpan.innerHTML = "";
}else{
encSpan.innerHTML = "";
}
})();
}
function startdecrypt(){
for(var z = 0; z < decodeSpans.length; z++){
decSpan = decodeSpans[z];
encSpan = encodeSpans[z];
var decrypted = document.getElementById(decSpan);
var encrypted = document.getElementById(encSpan);
encoder(decrypted, encrypted);
}
}
I only read up properly on closures like 2 nights ago, so still trying to put it all into context, so some (constructive) feedback on this solution would be welcome from those who know about these things :)

Categories

Resources