Blog: A Code Walkthrough – Live on a Million (More Posts)
I’m building an application that calculates how long a person can live on a given amount of money in a given number of years. Say, for example, £1million for the rest of your years. It’s just for a bit of fun and makes certain assumptions, like, no accruing of savings interests or other opportunities of personal income – and doesn’t cover one-time only expenses. It’s definitely not a substitute for financial advice 🙂
You fill in some initial text inputs and add as many more as you think you’ll need to live on through your life and select the number of years and see how this will affect your starting balance.
Let’s go through it up bit by bit.
We start with 6 input and label elements. Most of them are an initial list of annual expenses, each of which is editable. In the input fields you can enter any positive number. The remaining input element governs the number of years your expenses forecast will cover.
<?php require "header.php"; ?>
<h2 id="accumulated_total">£0.00</h2>
<main id="container">
<aside>
<span>Number of Years: </span>
<input type="number" id="num_years" class="num_years" min="0" value="£0.00" />
</aside>
<section class="expense_list">
<label for="" contenteditable="true">Electric</label>
<input type="number" id="" class="expense_value" min="0" value="0">
<p></p>
<label for="" contenteditable="true">Gas</label>
<input type="number" id="" class="expense_value" min="0" value="0">
<p></p>
<label for="" contenteditable="true">Water</label>
<input type="number" id="" class="expense_value" min="0" value="0">
<p></p>
<label for="" contenteditable="true">Council Tax</label>
<input type="number" id="" class="expense_value" min="0" value="0">
<p></p>
<label for="" contenteditable="true">TV</label>
<input type="number" id="" class="expense_value" min="0" value="0">
<p></p>
<a href="" class="btn" id="btn_addNew">Add New Expense</a>
</section>
<section class="save_list">
<a href="#" class="btn" id="btn_save">Save</a>
</section>
</main>
<?php require "footer.php"; ?>
Soon, we’ll be working with dynamic elements and changing data. But for now, we’ll start by calculating the total sum of the first 5 input elements. (.expense_value)
document.addEventListener('DOMContentLoaded', function() {
// Get all input elements with the class 'expense_value'
const expenseInputs = document.querySelectorAll('.expense_value');
// Add an input event listener to each expense input
expenseInputs.forEach(function(input) {
input.addEventListener('input', updateAccumulatedTotal);
});
// Initial update of the accumulated total
updateAccumulatedTotal();
function updateAccumulatedTotal() {
// Get the accumulated total element
const accumulatedTotalElement = document.getElementById('accumulated_total');
// Sum the values of all expense input fields
const totalValue = Array.from(expenseInputs).reduce(function(sum, input) {
return sum + parseFloat(input.value || 0);
}, 0);
// Update the accumulated total element
accumulatedTotalElement.textContent = `£${totalValue.toFixed(2)}`;
}
});
This takes the value of each of the input elements and returns to sum, an integer to the h2 element with the ID “accumulated_total“. And the number changes dynamically according to changes in each of the input elements.
What this means is we know that the total of the inputs is set up correctly and updated in real-time. But what we need is to have a constant variable, which is set to an integer, in this case, 1 million. That value will be the value of #accumulated_total – that is to say the highest possible value; a million. And then take the sum of the input fields and take that sum away from #accumulated_total.
// Set the constant value
const initialTotal = 1000000; // 1 million
function updateAccumulatedTotal() {
// Sum the values of all expense input fields
const totalValue = Array.from(expenseInputs).reduce(function(sum, input) {
return sum + parseFloat(input.value || 0);
}, 0);
// Calculate the remaining value
const remainingValue = initialTotal - totalValue;
// Update the accumulated total element
accumulatedTotalElement.textContent = `£${remainingValue.toFixed(2)}`;
}
Now, the #accumulated_total will start with 1 million, and as you update the input fields, it will subtract the sum of the input fields from the initial total.
It’s also worth having a calculated total of all annual expenses, just like we started with before.
I’ve added a new element that goes below the “Add New Expense” button, (.btn_addNew) button.
<div class="sum_total">Total: Items
<span id="amount">£0</span>
</div>
This element will display the sum total of all the input fields just like we did before but it will leave the #accumulated_total as it is now so 2 numbers that are updated in real time.
It gets more complicated from here because we’re going to be starting working with dynamic and changing data.
We’ve got our set of 5 expense totals from what is included in the DOM. Now, let’s multiply it with another input element that lets you select the number of years you forecast that you want to live your million on.
<aside>
<span>Number of Years: </span>
<input type="number" id="num_years" class="num_years" min="0" value="£0.00" />
</aside>
// Get the num_years input element
const numYearsInput = document.getElementById('num_years');
We then need to modify the accumulatedTotal function to multiply the million, by the calculated difference number of years.
function updateAccumulatedTotal() {
// Get the value of #num_years
const numYearsValue = parseFloat(numYearsInput.value) || 0;
// Get all input elements with the class 'expense_value'
const expenseInputs = document.querySelectorAll('.expense_value');
// Sum the values of all expense input fields
const totalValue = Array.from(expenseInputs).reduce(function(sum, input) {
return sum + parseFloat(input.value || 0);
}, 0);
// Calculate the remaining value considering num_years
const remainingValue = initialTotal - (totalValue * numYearsValue);
// Update the accumulated total element
accumulatedTotalElement.textContent = `£${remainingValue.toFixed(2)}`;
// Update the sum total element
sumTotalElement.textContent = `£${totalValue.toFixed(2)}`;
}
You can modify the script to take into account the value of #num_years and multiply it by the sum total of the input fields.
Now we’re going to need to add the ability to add new items to the totals list whilst also keeping the sum_total and accumulated_totals up to date. This is the tricky part because There’s a lot that can go wrong here since we’re dynamically adding new elements to the DOM.
Let’s start by selecting #btn_addNewand making sure the button no longer acts like a link and just adds a new label and input element.
Like this .
<label for="" contenteditable="true">TV</label>
<input type="number" id="" class="expense_value" min="0" value="0">
<p></p>
function addNewExpense() {
// Create a new label element
const newLabel = document.createElement('label');
newLabel.setAttribute('for', '');
newLabel.setAttribute('contenteditable', 'true');
newLabel.textContent = 'New Expense';
// Create a new input element
const newInput = document.createElement('input');
newInput.setAttribute('type', 'number');
newInput.setAttribute('class', 'expense_value');
newInput.setAttribute('min', '0');
newInput.setAttribute('value', '0');
newInput.addEventListener('input', updateAccumulatedTotal);
// Create a new paragraph element
const newParagraph = document.createElement('p');
// Insert the new elements before the add new button
const expenseList = document.querySelector('.expense_list');
expenseList.insertBefore(newLabel, addNewButton);
expenseList.insertBefore(newInput, addNewButton);
expenseList.insertBefore(newParagraph, addNewButton);
// Trigger an input event to update totals
updateAccumulatedTotal();
}
Now we have a fully functional single-page application that allows you to add in as many forecasted spending items that you like and it will work regardless of whether you added them to the application or not. Where we run into problems is where we try to implement save functionality which causes some issues with data not syncing properly. A problem to be tackled another day.
You can view the final version of the application here.


