Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

Lewis Marshall
Lewis Marshall
22,673 Points

Calculating input values with plain JavaScript

I'm practicing with plain JavaScript to create a case calculator.

I need help with the 'Time required' part were it calculates how many minutes it will take to complete with how many cases there is.

When you fill in just one input it fills in all of the 'Time required' boxes and adds them together.

How can I make it just fill in the box next to the input not all of the other boxes?

<table>
    <tr>
        <th>Layout Group</th>
        <th>Cases</th>
        <th>Time Required</th>
    </tr>
    <tr>
        <td>Cereals</td>
        <td><input onkeyup="totalSum()" type="text" value="0" /></td>
        <td><span></span></td>
    </tr>
    <tr>
        <td>Juice</td>
        <td><input onkeyup="totalSum()" type="text" value="0" /></td>
        <td><span></span></td>
    </tr>
    <tr>
        <td>Biscuits</td>
        <td><input onkeyup="totalSum()" type="text" value="0" /></td>
        <td><span></span></td>
    </tr>
    <tr>
        <th>total</th>
        <th><span id="totalCases"></span></th>
        <th><span id="totalTime"></span></th>
    </tr>
</table>           
const section = document.querySelector('section');
const input = document.querySelectorAll('input');
const total = document.querySelector('#totalCases');
const totalTime = document.querySelector('#totalTime');


//calculate total cases
const totalSum = () => {
    let sum = 0;
    let mins = 0;
    let hours = 0;
    let minsLeft = 0;
    let sum2 = 0;

    for (let i = 0; i < input.length; i++) {
        sum += parseInt(input[i].value);
        if (isNaN(sum)) {
            sum = 0;
        }

        mins += (input[i].value / 35) * 60;
        hours += Math.floor(mins / 60);
        minsLeft += mins % 60;


        let timeRequired = input[i].parentNode.nextElementSibling.children[0];
        timeRequired.innerHTML = `${hours.toFixed(0)} hours <br/>${minsLeft.toFixed(0)} mins`;
    }

    totalTime.innerHTML = `${mins.toFixed(0)} mins`;
    total.innerHTML = `${sum} cases`;
}

https://w.trhou.se/c9trpoelak

2 Answers

Hi Lewis,

I don't know what a "Case Calculator" is or what it is supposed to do. But, it looks the essence of your endeavor here is to sum some rows and sum some columns. If that is so, then the way you've currently coded it is fragile. This type of thing is where MVC could help you out a lot; at the very least, there needs to be a separation of concerns between the functions that are updating each row / column and the actual row/column data.

But, if you insist on doing things the spaghetti way, then you should at least be semantic about your naming and clear about the responsibilities of each function. Below I've cleaned up your code for you a little. Because I don't know what the purpose of this application is, the below code is far from perfect. But it works... sort of. Really, its intent is to guide you towards a more lucrative path.

const $section = document.querySelector('section');
const $inputs = document.querySelectorAll('input');
const $total = document.querySelector('#totalCases');
const $totalTime = document.querySelector('#totalTime');

function rowTotaler($input) {
    const sum = isNaN(parseInt($input.value)) ? 0 : parseInt($input.value)
    const minutes = (sum / 35 * 60).toFixed(0)
    const hours = Math.floor(minutes / 60).toFixed(0)
    const minutesLeft = (minutes % 60).toFixed(0)

    const $timeRequired = $input.parentNode.nextElementSibling.children[0]
    $timeRequired.innerHTML = `${hours} hours <br/>${minutesLeft} mins`

    return {
        "minutes": minutes,
        "sum": sum
    }
}

function updateRowsAndColumns(event) {
    const rowData = rowTotaler(event.target)

    var totalTime = isNaN(parseInt($totalTime.innerHTML)) ? 0 : parseInt($totalTime.innerHTML)
    var total = isNaN(parseInt($total.innerHTML)) ? 0 : parseInt($total.innerHTML)

    $totalTime.innerHTML = `${totalTime + rowData.minutes} mins`
    $total.innerHTML = `${total + rowData.sum} cases`
}

$inputs.forEach($input => {
    // NOTE: You'll need to remove the "onKeyup(totalSum)" calls in the HTML before running this
    $input.addEventListener('keyup', updateRowsAndColumns)
})

Let me know if you have any questions :)

Lewis Marshall
Lewis Marshall
22,673 Points

Thanks!

I did this application the first time with jQuery (http://www.marshy2201.karoo.net/casecalculator/). Its to calculate how long an aisle in a supermarket should take with the amount of cases that are coming.

I just thought of trying to do it in plain javascript just to get some practice with it.

Im struggling to understand how the .forEach() works and im guessing this is used instead of a for loop to get through the $inputs array?

Yeah, Array#forEach() is just a more readable way of looping through all the elements of a given array. It's an instance method on the Array object. It accepts an anonymous function that it calls on each element of the given array. Each time it calls the anonymous function it passes it three arguments: the current element, the current index and the original array itself.

So, I'm still not sure what "case" is referring to here. Like physical containers? Is this a race of some kind held in a supermarket?

In any case — lol pun unintended — you could certainly implement that web-app in vanilla JavaScript. It wouldn't be that hard, in fact. But, you would benefit from following an Object-Oriented approach. Such an approach will make your code more maintainable and less fragile, among other things.

If you decide to go down this road, you'll also want to make sure you separate your domain-modeling objects (read: the objects that describe the relationships between a "case", a "time", a "supermarket-lane" etc.) from the objects that control your views (read: the objects that create/manipulate the HTML/CSS like a "row", a "table" etc.). There is an architectural pattern for this called Model-View-Controller. Though, in JavaScript, anytime someone tells you something is MVC, they really mean that that something is MVP (Model-View-Presenter). But, that's another conversation for another time.

Of course if you're building a large, complex app, implementing a separated architecture from scratch can be tedious. This is why there are frameworks out there like Angular, EmberJS etc. Still, making something non-trivial in vanilla JavaScript can be a great way to learn the ins and outs of an architectural pattern like MVC or MVP.