ok, I want to tell you that, this is not a tutorial, it's a project using HTML, JavaScript and Bootstrap. I hope you are aware of all of them.

ok, let's build a project, it is an implementation of an expense tracker, which allows users to add, edit, and delete expenses.

let's start with HTML and the Bootstrap Part in this project.

Section-1 : it is the beginning of an HTML document. It includes a title for the page, metadata about the document's encoding and viewport, and a link to an external stylesheet from the Bootstrap library.

<!DOCTYPE html>
<html>
<head>
 <title>Expense Tracker</title>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">

Above section of an HTML file that includes metadata and a link to an external CSS stylesheet. Here's a breakdown of each line:

  • <!DOCTYPE html>: This line declares the document type as HTML5.
  • <html>: This tag marks the beginning of the HTML document.
  • <head>: This tag marks the beginning of the head section, which contains metadata about the document.
  • <title>Expense Tracker</title>: This line sets the title of the document to "Expense Tracker".
  • <meta charset="utf-8">: This line specifies the character encoding for the document as UTF-8, which allows for a wider range of characters to be displayed.
  • <meta name="viewport" content="width=device-width, initial-scale=1">: This line sets the viewport size of the document to the width of the device and scales the content to fit within that size.
  • <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">: This line links to an external CSS stylesheet hosted on a Content Delivery Network (CDN) that provides the Bootstrap 4.5.2 framework.

Section -2 : This section contains the main content of the page, including a form for adding expenses, a table for displaying expenses, and some Bootstrap classes for styling. The onload attribute on the body tag calls the showExpenses() function when the page is loaded.

The form tag contains three input fields for the date, category, and amount of the expense, as well as a hidden field for storing the index of the expense being edited. The button tag inside the form submits the form and calls the addExpense() function when clicked.

The table tag contains a header row and an empty tbody element for displaying the expenses. The header row has four columns: Date, Category, Amount, and Actions.

<body onload="showExpenses()">
 <div class="container">
  <div class="row">
   <div class="col-md-8 mx-auto">
    <h1>Expense Tracker</h1>
    <form onsubmit="return addExpense()">
     <input type="hidden" id="editIndex">
     <div class="form-group">
      <label for="date">Date:</label>
      <input type="date" id="date" class="form-control" required>
     </div>
     <div class="form-group">
      <label for="category">Category:</label>
      <input type="text" id="category" class="form-control" required>
     </div>
     <div class="form-group">
      <label for="amount">Amount:</label>
      <input type="number" id="amount" class="form-control" required>
     </div>
     <button type="submit" id="addButton" class="btn btn-primary">Add Expense</button>
    </form>
    <br>
    <table class="table table-striped table-bordered" id="expenseTable">
     <thead>
      <tr>
       <th>Date</th>
       <th>Category</th>
       <th>Amount</th>
       <th>Actions</th>
      </tr>
     </thead>
     <tbody></tbody>
    </table>
   </div>
  </div>
 </div>

Above body section of an HTML file that defines the structure and layout of an expense tracker web page. Here's a breakdown of each line:

  • <body onload="showExpenses()">: This line sets the onload attribute to the showExpenses() function, which will be called when the page is loaded and display the existing expenses in the expenseTable element.
  • <div class="container">: This tag creates a container that centers and adds margins to the content.
  • <div class="row">: This tag creates a row within the container.
  • <div class="col-md-8 mx-auto">: This tag creates a column within the row that occupies 8 out of 12 columns on medium-sized screens and centers it horizontally.
  • <h1>Expense Tracker</h1>: This line displays the heading "Expense Tracker".
  • <form onsubmit="return addExpense()">: This line creates a form that calls the addExpense() function when submitted.
  • <input type="hidden" id="editIndex">: This line creates a hidden input field with an id attribute of "editIndex", which will be used to store the index of the expense being edited.
  • <div class="form-group">: This tag creates a form group for the input field.
  • <label for="date">Date:</label>: This line displays a label for the date input field.
  • <input type="date" id="date" class="form-control" required>: This line creates a date input field with an id attribute of "date" and a class attribute of "form-control", which adds styling to the field. The required attribute indicates that the field must be filled out before submitting the form.
  • <div class="form-group">: This tag creates a form group for the input field.
  • <label for="category">Category:</label>: This line displays a label for the category input field.
  • <input type="text" id="category" class="form-control" required>: This line creates a text input field with an id attribute of "category" and a class attribute of "form-control", which adds styling to the field. The required attribute indicates that the field must be filled out before submitting the form.
  • <div class="form-group">: This tag creates a form group for the input field.
  • <label for="amount">Amount:</label>: This line displays a label for the amount input field.
  • <input type="number" id="amount" class="form-control" required>: This line creates a number input field with an id attribute of "amount" and a class attribute of "form-control", which adds styling to the field. The required attribute indicates that the field must be filled out before submitting the form.
  • <button type="submit" id="addButton" class="btn btn-primary">Add Expense</button>: This line creates a submit button with an id attribute of "addButton" and a class attribute of "btn btn-primary", which adds styling to the button.
  • <table class="table table-striped table-bordered" id="expenseTable">: This line creates a table with a class attribute of "table table-striped table-bordered" and an id attribute of "expenseTable".
  • <thead>: This tag creates the table header section.
  • <tr>: This tag creates a table row within the header section.
  • <th>Date</th>: This line creates a table heading cell with the text "Date".
  • <th>Category</th>: This line creates a table

Hurray, you completed the HTML and Bootstrap Part.

None
Hurray

Now, let's start with the JavaScript Part.

Section-1: addExpense() Function This function is called when the user submits the form for adding an expense. It retrieves the input values for the date, category, and amount, and the index of the expense to be edited (if any). If the editIndex value is empty, it adds the expense to local storage and the expense table. If the editIndex value is not empty, it updates the expense in local storage and the expense table. The function then calls resetForm() to clear the input fields and returns false to prevent the form from reloading the page.

<script>
  // add expense to table and local storage
  function addExpense() {
   var date = document.getElementById('date').value;
   var category = document.getElementById('category').value;
   var amount = document.getElementById('amount').value;
   var editIndex = document.getElementById('editIndex').value;

   if (editIndex === '') {
    var expenses = JSON.parse(localStorage.getItem('expenses')) || [];
    expenses.push({date: date, category: category, amount: amount});
    localStorage.setItem('expenses', JSON.stringify(expenses));
    var tableBody = document.getElementById('expenseTable').getElementsByTagName('tbody')[0];
    var row = '<tr><td>' + date + '</td><td>' + category + '</td><td>' + amount + '</td><td><button type="button" class="btn btn-sm btn-primary" onclick="editForm(this.parentNode.parentNode)">Edit</button> <button type="button" class="btn btn-sm btn-danger" onclick="deleteExpense(this.parentNode.parentNode)">Delete</button></td></tr>';
    tableBody.insertAdjacentHTML('beforeend', row);
   } else {
    editExpense(editIndex, date, category, amount);
   }

   resetForm();
   return false;
  }

Now, let's understand the addExpense() function code line by line.

function addExpense() {

This line declares a function named "addExpense". This function will be executed when a user adds a new expense to the system.

var date = document.getElementById('date').value;
var category = document.getElementById('category').value;
var amount = document.getElementById('amount').value;
var editIndex = document.getElementById('editIndex').value;

These lines retrieve the values of the "date", "category", "amount", and "editIndex" input fields from the HTML document.

if (editIndex === '') {

This line checks whether the "editIndex" variable is an empty string. If it is empty, it means that the user is adding a new expense, not editing an existing one.

var expenses = JSON.parse(localStorage.getItem('expenses')) || [];

This line retrieves the "expenses" data from the browser's local storage. If there are no expenses in the local storage, it creates an empty array.

expenses.push({date: date, category: category, amount: amount});
localStorage.setItem('expenses', JSON.stringify(expenses));

These lines add the new expense to the "expenses" array and save it back to the local storage in JSON format.

var tableBody = document.getElementById('expenseTable').getElementsByTagName('tbody')[0];
var row = '<tr><td>' + date + '</td><td>' + category + '</td><td>' + amount + '</td><td><button type="button" class="btn btn-sm btn-primary" onclick="editForm(this.parentNode.parentNode)">Edit</button> <button type="button" class="btn btn-sm btn-danger" onclick="deleteExpense(this.parentNode.parentNode)">Delete</button></td></tr>';
tableBody.insertAdjacentHTML('beforeend', row);

These lines retrieve the "expenseTable" element from the HTML document and add a new row to its "tbody" element with the details of the new expense. It also includes buttons to edit or delete the expense.

} else {
    editExpense(editIndex, date, category, amount);
}

If the "editIndex" variable is not empty, it means that the user is editing an existing expense. This line calls the "editExpense" function to update the expense in the "expenses" array.

resetForm();
return false;

These lines reset the input fields to their initial state and return false to prevent the form from submitting and reloading the page.

Section-2 : resetForm() Function This function resets the input fields and editIndex value to their default values, and changes the button text to "Add Expense".

// reset the form
  function resetForm() {
   document.getElementById('date').value = '';
   document.getElementById('category').value = '';
   document.getElementById('amount').value = '';
   document.getElementById('editIndex').value = '';
   document.getElementById('addButton').innerHTML = 'Add Expense';
  }

Now, let's understand the resetForm() function code line by line.

// reset the form
function resetForm() {

This line declares a function named "resetForm", which is used to reset the values of the input fields on the HTML form.

document.getElementById('date').value = '';
document.getElementById('category').value = '';
document.getElementById('amount').value = '';
document.getElementById('editIndex').value = '';

These four lines reset the values of the "date", "category", "amount", and "editIndex" input fields on the HTML form to an empty string.

document.getElementById('addButton').innerHTML = 'Add Expense';

This line sets the innerHTML property of the HTML element with the ID "addButton" to the string "Add Expense". This changes the text displayed on the button from "Edit Expense" (if the user was previously editing an expense) to "Add Expense" (indicating that the form is now ready to add a new expense).

Section-3: deleteExpense(row)() function This function is called when the user clicks the delete button for an expense. It removes the expense row from the table and deletes the expense from local storage.


  // delete expense from table and local storage
  function deleteExpense(row) {
     row.parentNode.removeChild(row);
     var expenses = JSON.parse(localStorage.getItem('expenses'))
     var rowIndex = row.rowIndex - 1;
     expenses.splice(rowIndex, 1);
     localStorage.setItem('expenses', JSON.stringify(expenses));
     }

let's understand the deleteExpense(row) function code line by line.

function deleteExpense(row) {

This line defines a function called deleteExpense that takes a parameter called row.

row.parentNode.removeChild(row);

This line removes the specified row element from the DOM (Document Object Model) by calling the removeChild method on its parent node.

var expenses = JSON.parse(localStorage.getItem('expenses'))

This line retrieves the current list of expenses from the local storage by calling localStorage.getItem('expenses'). The value of expenses is a JSON string, so JSON.parse is used to convert it into a JavaScript object.

var rowIndex = row.rowIndex - 1;

This line gets the index of the row to be deleted in the table. The rowIndex property of the row element gives its position within the table. The -1 adjustment is needed because the index starts from 0 but the expenses array in local storage starts from 1.

expenses.splice(rowIndex, 1);

This line removes the expense at the rowIndex position from the expenses array using the splice method. The second argument 1 specifies that only one item should be removed from the array.

localStorage.setItem('expenses', JSON.stringify(expenses));
  • This line updates the expenses array in the local storage by calling localStorage.setItem. The first argument is the key ('expenses') used to store the data, and the second argument is the updated expenses array, which is converted to a JSON string using JSON.stringify. This ensures that the data is stored in the correct format for local storage.

Section-4: editForm(row) function This function is called when the user clicks the edit button for an expense. It retrieves the expense index from the table row and populates the input fields with the expense values. It also changes the button text to "Update Expense" and sets the editIndex value to the expense index.

// edit expense in form
 function editForm(row) {
    var rowIndex = row.rowIndex - 1;
    var expenses = JSON.parse(localStorage.getItem('expenses')) || [];
    var expense = expenses[rowIndex];
    document.getElementById('date').value = expense.date;
    document.getElementById('category').value = expense.category;
    document.getElementById('amount').value = expense.amount;
    document.getElementById('editIndex').value = rowIndex;
    document.getElementById('addButton').innerHTML = 'Update Expense';
   }

let's understand the editForm(row) function code line by line.

function editForm(row) {

This line defines a function called editForm that takes a parameter called row.

var rowIndex = row.rowIndex - 1;

This line gets the index of the row to be edited in the table. The rowIndex property of the row element gives its position within the table. The -1 adjustment is needed because the expenses array in local storage starts from 0 but the row index starts from 1.

var expenses = JSON.parse(localStorage.getItem('expenses')) || [];

This line retrieves the current list of expenses from the local storage by calling localStorage.getItem('expenses'). The value of expenses is a JSON string, so JSON.parse is used to convert it into a JavaScript object. If there are no expenses stored in local storage, an empty array is assigned to expenses.

var expense = expenses[rowIndex];

This line retrieves the expense object at the rowIndex position from the expenses array.

document.getElementById('date').value = expense.date;
document.getElementById('category').value = expense.category;
document.getElementById('amount').value = expense.amount;

These three lines set the value of the date, category, and amount fields in the expense form to the corresponding values from the expense object.

document.getElementById('editIndex').value = rowIndex;

This line sets the value of a hidden input field called editIndex to the rowIndex value. This is used to keep track of which expense is being edited when the form is submitted.

document.getElementById('addButton').innerHTML = 'Update Expense';

This line changes the label of the submit button to 'Update Expense' to indicate that the form is in edit mode. This helps the user to understand that the changes they make will update an existing expense rather than creating a new one.

Section-5: editExpense(index, date, category, amount) function This function is called when the user updates an existing expense. It updates the expense in local storage and the expense table.

// edit expense in table and local storage
 function editExpense(index, date, category, amount) {
    var expenses = JSON.parse(localStorage.getItem('expenses')) || [];
    expenses[index] = {date: date, category: category, amount: amount};
    localStorage.setItem('expenses', JSON.stringify(expenses));
    var tableRow = document.getElementById('expenseTable').rows[index+1];
    tableRow.cells[0].innerHTML = date;
    tableRow.cells[1].innerHTML = category;
    tableRow.cells[2].innerHTML = amount;
    resetForm();
   }

let's understand the editExpense(index, date, category, amount) function code line by line.

function editExpense(index, date, category, amount) {

This line defines a function called editExpense that takes four parameters: index (the index of the expense to be edited), date, category, and amount (the updated values for the expense).

var expenses = JSON.parse(localStorage.getItem('expenses')) || [];

This line retrieves the current list of expenses from the local storage by calling localStorage.getItem('expenses'). The value of expenses is a JSON string, so JSON.parse is used to convert it into a JavaScript object. If there are no expenses stored in local storage, an empty array is assigned to expenses.

expenses[index] = {date: date, category: category, amount: amount};

This line updates the expense object at the index position in the expenses array with the new date, category, and amount values.

localStorage.setItem('expenses', JSON.stringify(expenses));

This line updates the expenses array in the local storage by calling localStorage.setItem. The first argument is the key ('expenses') used to store the data, and the second argument is the updated expenses array, which is converted to a JSON string using JSON.stringify. This ensures that the data is stored in the correct format for local storage.

var tableRow = document.getElementById('expenseTable').rows[index+1];

This line gets the table row element corresponding to the updated expense by using the getElementById method on the table element and accessing the rows array with the index+1 value. The index+1 adjustment is needed because the first row in the table is the header row and not an expense row.

tableRow.cells[0].innerHTML = date;
tableRow.cells[1].innerHTML = category;
tableRow.cells[2].innerHTML = amount;

These three lines update the date, category, and amount cells in the selected row with the new values from the parameters.

resetForm();
  • This line resets the expense form to its default state by calling the resetForm function. This clears any existing values in the form fields and changes the label of the submit button back to 'Add Expense'.

Section-6: showExpenses() function This function is called when the page loads. It retrieves the expenses from local storage and populates the expense table with them.


function showExpenses() {
  var expenses = JSON.parse(localStorage.getItem('expenses')) || [];
  var tableBody = document.getElementById('expenseTable').getElementsByTagName('tbody')[0];
  for (var i = 0; i < expenses.length; i++) {
   var expense = expenses[i];
   var row = '<tr><td>' + expense.date + '</td><td>' + expense.category + '</td><td>' + expense.amount + '</td><td><button type="button" class="btn btn-sm btn-primary" onclick="editForm(this.parentNode.parentNode)">Edit</button> <button type="button" class="btn btn-sm btn-danger" onclick="deleteExpense(this.parentNode.parentNode)">Delete</button></td></tr>';
   tableBody.insertAdjacentHTML('beforeend', row);
  }
 }
</script>
</body>
</html>

now, let's understand the showExpenses() function line by line.

function showExpenses() {

This line defines a function called showExpenses.

var expenses = JSON.parse(localStorage.getItem('expenses')) || [];

This line retrieves the current list of expenses from the local storage by calling localStorage.getItem('expenses'). The value of expenses is a JSON string, so JSON.parse is used to convert it into a JavaScript object. If there are no expenses stored in local storage, an empty array is assigned to expenses.

var tableBody = document.getElementById('expenseTable').getElementsByTagName('tbody')[0];

This line gets the table body element of the expenseTable by using getElementById method on the table element and accessing the getElementsByTagName method on the table body element.

for (var i = 0; i < expenses.length; i++) {

This line sets up a for loop that iterates over all the expenses in the expenses array.

var expense = expenses[i];

This line retrieves the expense at the current index of the loop.

var row = '<tr><td>' + expense.date + '</td><td>' + expense.category + '</td><td>' + expense.amount + '</td><td><button type="button" class="btn btn-sm btn-primary" onclick="editForm(this.parentNode.parentNode)">Edit</button> <button type="button" class="btn btn-sm btn-danger" onclick="deleteExpense(this.parentNode.parentNode)">Delete</button></td></tr>';

This line creates a new HTML table row element with four cells: one for the date, one for the category, one for the amount, and one for buttons to edit or delete the expense. The buttons are created with onclick attributes that call the editForm or deleteExpense functions with the row element as a parameter.

tableBody.insertAdjacentHTML('beforeend', row);

This line inserts the newly created row element into the table body using the insertAdjacentHTML method. The beforeend position ensures that the new row is added at the end of the table body.

Section-7: Some concept that are used in this project

Local Storage — The localStorage object is used to store the expenses as a JSON string. The JSON.parse() and JSON.stringify() methods are used to convert the data between a string and an object.

Event Listeners — The onsubmit, onclick, and onload event listeners are used to call the appropriate functions when the user interacts with the form or table.

ok, we did it, we completed the project.

None

if you want the full code — click here

connect with me- GitHub , Instagram