$Nan? My age old enemy (convert strings back into numbers?)

Ok, so I’ve been back to the drawing board. After a lot of reading and watching tutorials, I rewrote my code so all of my { and ; and ( are in the right place and (almost) everything makes sense to me. I am now having trouble with $Nan. I don’t know why my numbers are strings but I would appreciate a second opinion. Is there a fix that I am just completely snoozing on?

HTML

<div id="checkboxContainer">
	<table>
		<tr>
			<th></th>
			<th></th>
			<th></th>
		</tr>
		<tr>
			<td><input type="checkbox" name="checkbox" value= "556.23" /></td>
			<td>4 Applications</td> 
			<td>556.23</td>
		</tr>
		<tr>
			<td><input type="checkbox"  name="checkbox" value="556.23" /></td>
			<td>3 Applications</td>
			<td>556.23</td>
		</tr>
		<tr>
			<td><input type="checkbox" name="checkbox" value="556.23" /></td>
			<td> Aeration</td>
			<td>556.23</td>
		</tr>
		<tr>
			<td><input type="checkbox" name="checkbox" value="548.23" /></td>
			<td>Aeration Overseed</td>
			<td>548.23</td>
		</tr>
		<tr>
			<td><input type="checkbox" name="checkbox" value="556.23" /></td>
			<td>C20 Application</td>
			<td>556.23</td>
		</tr>
	</table>
</div>

JavaScript

const subTotal = document.querySelector("#subTotal");
const checkboxContainer = document.querySelector("#checkboxContainer");
let total = 0;

const formatter = new Intl.NumberFormat("en-US", {
    style: "currency",
    currency: "USD",
});

checkboxContainer.addEventListener("change", e => {
    if (e.target.tagName == "INPUT") {
        const floatValue = parseFloat(e.target.value);
        if (e.target.checked) {
            total += floatValue;
        } else {
            total -= floatValue;
        }
    }
    subTotal.innerHTML = formatter.format("total");
    
});

Here’s your culprit

subTotal.innerHTML = formatter.format("total");

You are passing in a string instead of the total variable’s value

This should fix it

subTotal.innerHTML = formatter.format(total);
1 Like

Question, is there a reason that you are using tables? Is this part of something bigger, where tables make sense?

If it is just for layout, then it might be better to use a form and a more semantic approach.

I have been having a play. This is your table using a form instead. I have made use of CSS grid to give it a table like structure.

<div class='container'>
    <form action='' id='tax-calculator'>
        <h2>Tax Calculator</h2>
        <fieldset id='applications' class='form-fieldset'>
            <div class='title-headings'>
                <h4>Type</h4>
                <h4>Price</h4>
            </div>
            <div class='checkbox'>
                <input type='checkbox' id='applications-4' name='applications-4' value='556.23'>
                <label for='applications-4' >4 Applications</label>
                <span class='checkbox-value'>556.23</span>
            </div>
            <div class='checkbox'>
                <input type='checkbox' id='applications-3' name='applications-3' value='556.23'>
                <label for='applications-3'>3 Applications</label>
                <span class='checkbox-value'>556.23</span>
            </div>
            <div class='checkbox'>
                <input type='checkbox' id='aeration' name='aeration' value='556.23'>
                <label for='aeration'>Aeration</label>
                <span class='checkbox-value'>556.23</span>
            </div>
            <div class='checkbox'>
                <input type='checkbox' id='aeration-overseed' name='aeration-overseed' value='548.23'>
                <label for='aeration-overseed'>Aeration Overseed</label>
                <span class='checkbox-value'>548.23</span>
            </div>
            <div class='checkbox'>
                <input type='checkbox' id='c20-application' name='c20-application' value='556.23'>
                <label for='c20-application'>C20 Application</label>
                <span class='checkbox-value'>556.23</span>
            </div>
        </fieldset>
    </form>
    <h3>Total:</h3>
    <output id='total'>$0.00</output>
</div>

I haven’t tried your table layout for accessibility, but my approach does allow for navigation using keys, tab to tab down, shift-tab to tab up and spacebar to toggle the checkboxes.

The other nice thing in my opinion is that in the DOM the fieldset has an elements property, which is a HTMLCollection of all it’s inputs. This makes it easy to select those elements in Javascript.

Here is my Javascript revision

// helper methods

// shorthand for Array.prototype.flatMap
const flatMap = [].flatMap;

// sums the total of an array of numbers
const sum = (nums) => nums.reduce((x, y) => x + y, 0);

const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });

const form = document.querySelector('#tax-calculator');
const output = document.querySelector('output#total');

form.addEventListener('change', function (event) {
    // find the closest fieldset parent of the changed element
    const fieldset = event.target.closest('fieldset');
    // fieldsets have an elements property that contains an HTMLCollection of it's input elements
    // try console.dir(fieldset.elements) to see what it looks like
    const values = flatMap.call(
        fieldset.elements,
        (elem) => elem.checked ? [parseFloat(elem.value)] : []
    );

    output.textContent = formatter.format(sum(values));
});

A couple of things. I have created a sum helper function called sum to total the checked values.

// sums the total of an array of numbers
const sum = (nums) => nums.reduce((x, y) => x + y, 0);

This function takes an array of numbers, adds them up and returns the total.
e.g.

sum([1,2,3]) → 6

You can read about the reduce function here

Again I have used flatMap to filter and map, in this case to filter the checked values.

// find the closest fieldset parent of the changed element
const fieldset = event.target.closest('fieldset');
// fieldsets have an elements property that contains an HTMLCollection of it's input elements
const values = [].flatMap.call(
    fieldset.elements,
    // empty arrays are discarded in the flattening process
    // using this we can filter results
    (elem) => elem.checked ? [parseFloat(elem.value)] : []
);

Just another approach @info3382

Codepen here

1 Like
document.getElementById("tax-calculator").addEventListener("change", () => {
 document.getElementById("total").textContent = formatter.format([...document.querySelectorAll("#tax-calculator input")].reduce((a,x) => a + (x.value * x.checked),0));
}

(I dont care what element triggered the change. I dont care if the code executes multiple times. I dont need to introduce new types. I dont care about making it parse values. Coercion will do it for me.)

1 Like

e.g. “556.23” * true → 556.23 or * false → 0

I had to test that coercion out.

I’m guessing equivalent to
“556.23” * 1 → 556.23 or * 0 → 0

1 Like

Okay so I realize I should probably explain a bit so that learning can happen. Let me break down what I did there.

document.getElementById("tax-calculator").addEventListener("change", () => {

This should be pretty self-explanatory to you by now, but, for nuance sake: I’m triggering this on any change made to the form. The form has an ID. So I can getElementById, which should be shorter than querySelector’ing the whole document, because the system knows its looking for an ID.

document.getElementById("total").textContent = formatter.format([...
document.querySelectorAll("#tax-calculator input")].reduce((a,x) => a + 
(x.value * x.checked),0));

Right. A lot going in there. So i’m gonna break it down like.

Again, grabbing by ID. You gave it an ID for a reason. Use it.
What we’re manipulating. And for those in the peanut gallery, yes, <output> is a standard element in the HTML standard.
I dont think i need to explain this, but formatter must already be defined.
Because querySelectorAll returns an HTMLCollection, rather than an array, the Spread (…) syntax inside an array marker will transform the collection into an array. This is because we want to use reduce to do our combining.
Select all the checkboxes. We’ll need them all to do the total. Again, this returns an HTMLCollection by default.
Reduce takes an array, runs a function across it, and returns the singular output. The interior function takes two parameters: an Accumulator (I’ve called mine a) and the current value in the array(x).
The function needs to compile the values onto the accumulator. We’re doing math, so a + (some new value) will sum our values as we go.
Here’s where the coercion comes into play. Javascript sees that we’re doing some multiplication, so it’s going to need numbers on both sides of that symbol, or its not going to make sense. It sees x.value (lets say “556.23”), which is a string, so it stuffs it into a number converter, and comes out with… well, a number (556.23). All good. It then looks at the right side, and sees a boolean. Stuffing a boolean into a Number is relatively simple: false is 0, true is 1. So we can do x.value * x.checked, and for each checkbox, x.value will always be the value of the checkbox, and x.checked will either multiply it by 0 (yielding 0), or 1 (yielding the value).
A reduce function can’t start adding things to an accumulator if that accumulator doesnt have an initial value. Here, we tell the accumulator to start at 0. Because we start at 0 when doing a sum.

1 Like

You’re doing the same as I have done in my example @m_hutley, with the difference being type coercion and a different way of navigating the DOM to get the inputs.

BTW yellow and cyan doesn’t work on a white background. Giving me a migrane lol.

2 Likes

I ran out of colors. Also what coder uses a white background? :stuck_out_tongue_winking_eye:

1 Like

Hadn’t considered that :lol:

1 Like

Maybe when Disourse implements some kind of… footnoting feature for posts? But then that just turns into a spam problem… hrm.

EDIT: Apparently it already exists, and is in the core features if a forum enables it… hrm…[1]


  1. Testing ↩︎

1 Like

Okay, so let me try that using Discourse Footnotes instead, and maybe get some feedback on which we think works better.

document.getElementById("total")[1].textContent[2] = formatter.format[3]([...[4]document.querySelectorAll("#tax-calculator input")[5]].reduce((a,x)[6]=> a +[7]
(x.value * x.checked[8]),0[9]))

EDIT also adjusts the colors in the original post to avoid migraines for those in the bright world


  1. Again, grabbing by ID. You gave it an ID for a reason. Use it. ↩︎

  2. What we’re manipulating. And for those in the peanut gallery, yes, <output> is a standard element in the HTML standard. ↩︎

  3. I dont think i need to explain this, but formatter must already be defined. ↩︎

  4. Because querySelectorAll returns an HTMLCollection, rather than an array, the Spread (…) syntax inside an array marker will transform the collection into an array. This is because we want to use reduce to do our combining. ↩︎

  5. Select all the checkboxes. We’ll need them all to do the total. Again, this returns an HTMLCollection by default. ↩︎

  6. Reduce takes an array, runs a function across it, and returns the singular output. The interior function takes two parameters: an Accumulator (I’ve called mine a ) and the current value in the array(x ). ↩︎

  7. The function needs to compile the values onto the accumulator. We’re doing math, so a + (some new value) will sum our values as we go. ↩︎

  8. Here’s where the coercion comes into play. Javascript sees that we’re doing some multiplication, so it’s going to need numbers on both sides of that symbol, or its not going to make sense. It sees x.value (lets say “556.23”), which is a string, so it stuffs it into a number converter, and comes out with… well, a number (556.23). All good. It then looks at the right side, and sees a boolean. Stuffing a boolean into a Number is relatively simple: false is 0, true is 1. So we can do x.value * x.checked , and for each checkbox, x.value will always be the value of the checkbox, and x.checked will either multiply it by 0 (yielding 0), or 1 (yielding the value). ↩︎

  9. A reduce function can’t start adding things to an accumulator if that accumulator doesnt have an initial value. Here, we tell the accumulator to start at 0. Because we start at 0 when doing a sum. ↩︎

1 Like

I don’t know why I didn’t think about using a form :person_facepalming:. My only defense is that I also answer the phones at work and I cannot keep a concise string of thoughts together. A form makes a lot more sense. Even with everything else in my tax calculator the form is way better. Thank you so much for breaking it down like you did.

1 Like

Thank you for breaking it down like you did! It helps with my understanding a lot! Thank you for going through the trouble of writing this out the way you did.

In a technical sense, this is the solution but I really do appreciate everything else people have added to this thread!

1 Like