Compounding 101 - learning JavaScript along the way

As a continuation to my last post, Approaching the Problem, I had time recently to sit down and put "pen to paper" so to speak. As a learning initiative, I set out to build a simple savings calculator from start to finish. I'm pleased to report that I've got something up and running. Much credit to the many StackOverflow threads, Mozilla Developer docs, and some of my more qualified programmer friends for the help. Feel free to play around with it in its current state. Note that there isn't a single line of CSS (although the chart is mildly styled). You can view the full source from JSFiddle.

I did have a few challenges along the way. Unfortunately I didn't document them in the moment, but here's a short list of them.

1) Converting an object to single set of values

In order to handle the calculation for each data point, there's a computed section within my Vue instance that runs a for loop, calculating the relevant totals and ages at each time frame. In my case, I'm storing two different key: value pairs, total and age. Since this is an object, I can't inherently just immediately pass that object to ChartJS and expect to be able to read it. Additionally, I need to be able to separate out the values as total becomes the y-axis data, and age becomes the labels (x-axis).

It look me awhile to figure this out, and again, credit to everyone else that ever had this problem and documented it, as I was able to use object.values with map to return only one set of the values from the pair.

In case you're curious, this is what that bit of code looks like:
Object.values(demo.timeData).map(function (a) {return a.total});

A few quick notes on there:

  1. Object.values is a Javascript function that converts my object into an array, which can then be used with .map which can iterate though the array, returning only those values for which the key was total.
  2. demo.timeData refers to the actual data that VueJS is calculating, as demo is the element (or el in Vue), and timeData is the computed object.
  3. function (a) {return a.total} is the ES5 (thus IE supported) syntax of defining such a simple function to only return the total value. In ES6 supported browers (IE8+ and everything else), this could have been written as (a => a.total)

2) Finding data in the DOM

Sometimes I had cases where I thought I was updating the correct value, and I'd have a console.log setup to spit it out, yet when it ran, it didn't actually update the correct values. The underlying issue here is that I wasn't properly using Chart.js's options for updating chart data.

In my limited understanding, I thought that if I directly updated data variable I was using in Chart.js, then called chart.update();

In my limited understanding, I thought that if I directly updated data variable I was using in Chart.js, then called chart.update();, it would pickup the new data and labels I was passing along as Vue recalculated the information. Unfortunately that wasn't the case, and produced this odd scenario wherein I could use the console to read totalresult (which was my variable for the data itself), and it'd return the original values that are calculated when the page loaded originally, but if I used console.log to dump totalresult when then onChange event was triggered, it would dump the updated calculation of the numbers.

For reasons I never truly understand, it's as though the totalresult existed in two places, likely one inside my Vue instance, and one outside. As such, it gave the impression that my data wasn't updating as expected.

Turns out, to update the data that the chart was pulling, I needed to directly update the dataset (and labels) that myChart had stored:

  myChart.data.datasets[0].data = Object.values(demo.timeData).map(function(a) {
    return a.total
  });
  myChart.data.labels = Object.values(demo.timeData).map(function(a) {
    return a.age
  });

You can even see above the Object.values and map function that I explained in my previous point.


3) Ternary vs if, else statement

Here's a screenshot of some of my original code that was used to calculate the actual data from the chart. At first glance, it made sense to me: if $i (an incrementing number) was 0, then total is equal to whatever you started with, this.initialCapital. However, once $i > 0, then you'd need to do a calculation of the total.

However, this didn't work. Console told me there was an Unexpected token if.

After syncing with a friend of mine, he told me that I couldn't use a conditional like this, and I'd need to use a ternary. While I was unable to find clear documentation as to why the conditional wouldn't work, changing the expression to a tenery fixed the problem.

I'll also mention that what you're seeing above, even changing with a ternary, doesn't fully solve the problem. Only the first value of the set returns correctly. The flaw in this approach is this.total inside the total calculation. Based on my understanding, I was trying to access the object with the data as this, but you can't do that. Instead, I'd need to pull the value out of the loop so that it can keep its value and increase throughout the loop.


4) data[i] vs data[i - 1]

Although I ended up going a different direction, based on the above, wherein I pulled the total out of the loop altogether, I did explore my original approach, which wrongly used this.total inside the loop.

If I had wanted to truly use total inside the loop, I'd need to access it as a subset of data. So instead of writing this.total, I wrote data[i].total.

(if you have a keen eye, you can see I dropped the "$" from my i variable)

Turns out that data[i].total still doesn't work:

Error in render: "TypeError: Cannot read property 'total' of undefined"

It doesn't work because I'm still in the process of defining that index. So as the code looped and did data[2], because it hasn't been defined, trying to access the .total of an undefined index, is undefinied. Using data[i-1] solves this problem. So for example, when years_to_go == 2, you want to base the total off of years_to_go == 1. As such, you need the -1 in the data, or else you're trying to access a value (the total), that does not yet exist in the loop.


As a small side note, I ended up not using vue-chartjs, the Chart.js wrapper for Vue.js. Although I found a lot of references in much of the material I found, it was pretty clear to me that using it would be a bit overkill, especially for a single chart. For implementations that utilize multiple charts, with varying sets of data and styling, this wrapper would tremendously lift that burden. In my case, it was unnecessary.


To wrap up this project, I'll move forward with making the calculations a little more complex, and adding some styling so it can be a standalone page, rather than just a bare set of inputs.