// primitive types
let name = "Vinod"; // string
let balance = 1445.50; // number
let isActive = true; // boolean
const FEE = 0.02; // constant — never changes
let cashback = null; // empty value
let lastTxn; // undefined — declared but not assigned
// reference types
let transactions = [500, -200, 300]; // array
let user = { name: "Vinod", balance: 1445 }; // object// arithmetic
user.balance += 500; // add
user.balance -= 200; // subtract
// comparison — returns true/false
amount > balance // greater than
amount === balance // strict equal — checks value AND type
amount !== null // not equal
// logical
isPremium && amount <= 1000 // AND — both must be true
isPremium || amount <= 1000 // OR — at least one must be true// 1. declaration
function greetUser(name) {
return `Hello, ${name}!`;
}
// 2. expression
const calculateCashback = function(amount) {
return amount >= 500 ? amount * 0.05 : 0;
};
// 3. arrow function — modern shorthand
const isAllowed = (amount) => {
return user.isPremium || amount <= 1000;
};// Transaction class — represents one transaction
class Transaction {
constructor(amount, type, date) {
this.amount = amount;
this.type = type;
this.date = date;
}
getDetails() {
return `${this.type.toUpperCase()} of ₹${this.amount} on ${this.date}`;
}
}
// Wallet class — manages balance and transactions
class Wallet {
constructor(initialBalance = 0) {
this.balance = initialBalance;
this.transactions = [];
}
addTransaction(amount, type) {
// validation
if (typeof amount !== "number" || amount <= 0) return;
if (type !== "credit" && type !== "debit") return;
const txn = new Transaction(amount, type,
new Date().toISOString().split("T")[0]);
this.transactions.push(txn);
if (type === "credit") {
this.balance += amount;
} else {
if (amount > this.balance) { console.log("Insufficient balance"); return; }
this.balance -= amount;
}
}
getTotalCredits() {
return this.transactions
.filter(t => t.type === "credit")
.reduce((sum, t) => sum + t.amount, 0);
}
getTotalDebits() {
return this.transactions
.filter(t => t.type === "debit")
.reduce((sum, t) => sum + t.amount, 0);
}
getCashback() {
const credits = this.getTotalCredits();
return credits >= 500 ? credits * 0.05 : 0;
}
}// 1. SELECT
document.getElementById("wallet-balance") // by id — one element
document.querySelector(".form-grid") // by CSS selector — first match
document.querySelectorAll(".stat-card") // all matching elements
// 2. MODIFY
element.textContent = "₹ 1,940.50" // change text
element.innerHTML = "<span>Active</span>" // change HTML inside
element.setAttribute("href", "/dashboard") // change attribute
element.classList.add("text-success") // add CSS class
element.classList.remove("text-danger") // remove CSS class
// 3. EVENTS
element.addEventListener("click", function() { ... })
element.addEventListener("submit", function(e) { e.preventDefault(); ... })
// 4. CREATE and REMOVE
const row = document.createElement("tr") // create new element
row.innerHTML = `<td>...</td>` // add content
tbody.appendChild(row) // add to page
tbody.insertBefore(row, tbody.firstChild) // add at top
element.remove() // remove from pagefunction renderTransaction(transaction) {
const tbody = document.getElementById("transactions-table");
const row = document.createElement("tr");
const isCredit = transaction.type === "credit";
row.innerHTML = `
<td>${transaction.date}</td>
<td>
<span class="badge ${isCredit ? 'badge-green' : 'badge-red'}">
${isCredit ? 'Credit' : 'Debit'}
</span>
</td>
<td class="${isCredit ? 'text-green' : 'text-red'}">
${isCredit ? '+' : '−'} ${transaction.amount.toFixed(2)}
</td>
`;
tbody.insertBefore(row, tbody.firstChild); // newest on top
}| Event | When it fires | Used in wallet app |
|---|---|---|
click |
element clicked | Add transaction button |
submit |
form submitted | Transaction form |
change |
input value changes | Account type dropdown |
mouseover |
mouse enters element | Stat card hover |
mouseout |
mouse leaves element | Reset card color |
keydown |
key pressed | Enter shortcut |
keyup |
key released | Amount preview |
focus |
element gains focus | Input border turns blue |
blur |
element loses focus | Input border resets |
form.addEventListener("submit", function(e) {
e.preventDefault(); // stops page refresh
// handle form yourself
});// without delegation — one listener per element
document.getElementById("home").addEventListener("click", ...)
document.getElementById("profile").addEventListener("click", ...)
document.getElementById("settings").addEventListener("click", ...)
// with delegation — one listener on parent handles all
document.getElementById("menu").addEventListener("click", function(event) {
if (event.target.tagName === "LI") {
alert(`You clicked ${event.target.textContent}`);
}
});Why it matters — if you add new <li> items later, they automatically work. No new listeners needed.
// synchronous — blocks everything
console.log("Start");
alert("Fetching..."); // freezes the page
console.log("End");
// asynchronous — non-blocking
console.log("Start");
setTimeout(() => console.log("Fetched!"), 2000);
console.log("End");
// prints: Start → End → Fetched! (after 2s)1. Callbacks — oldest
function fetchData(callback) {
setTimeout(() => {
callback({ name: "Vinod", balance: 1445 });
}, 2000);
}
fetchData(function(data) {
console.log(data); // runs after 2s
});2. Promises — cleaner
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) resolve("Data fetched!");
else reject("Error!");
}, 2000);
});
}
fetchData()
.then(result => console.log(result)) // success
.catch(error => console.error(error)); // failure3. Async/Await — modern, reads like normal code
async function getData() {
try {
const result = await fetchData(); // waits here
console.log(result);
} catch (error) {
console.error(error);
}
}// in Wallet class
addTransactionAsync(amount, type) {
return new Promise((resolve, reject) => {
document.getElementById("loading-text").style.display = "block";
setTimeout(() => {
try {
this.addTransaction(amount, type);
const txn = this.transactions[this.transactions.length - 1];
resolve(txn);
} catch (err) {
reject(err);
}
}, 2000);
});
}
// in form handler
transactionForm.addEventListener("submit", async function(e) {
e.preventDefault();
const amount = parseFloat(document.getElementById("amount").value);
const type = document.querySelector('input[name="type"]:checked').value;
try {
const txn = await myWallet.addTransactionAsync(amount, type);
document.getElementById("wallet-balance").textContent = `₹ ${myWallet.balance.toFixed(2)}`;
renderTransaction(txn);
document.getElementById("loading-text").style.display = "none";
} catch (error) {
document.getElementById("loading-text").textContent = "Error: " + error;
}
});// .then() chain
fetch("https://api.example.com/data")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
// async/await — preferred
async function getData() {
try {
const response = await fetch("https://api.example.com/data");
if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}// GET — read
fetch("https://mockapi.io/transactions")
// POST — create
fetch("https://mockapi.io/transactions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ type: "credit", amount: 500 })
})
// PUT — update
fetch("https://mockapi.io/transactions/5", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ type: "debit" })
})
// DELETE — remove
fetch("https://mockapi.io/transactions/5", { method: "DELETE" })let myWallet; // declared globally — initialized after fetch
// LOAD transactions on page start
async function loadTransactions() {
const response = await fetch("https://mockapi.io/transactions");
const transactions = await response.json();
let balance = 0, credits = 0, debits = 0;
transactions.forEach(txn => {
if (txn.type === "credit") { credits += txn.amount; balance += txn.amount; }
else { debits += txn.amount; balance -= txn.amount; }
});
myWallet = new Wallet(balance); // initialize AFTER fetch
localStorage.setItem("walletBalance", myWallet.balance);
document.getElementById("wallet-balance").textContent = `₹ ${balance.toFixed(2)}`;
document.getElementById("total-credits").textContent = `₹ ${credits.toFixed(2)}`;
document.getElementById("total-debits").textContent = `₹ ${debits.toFixed(2)}`;
const tbody = document.getElementById("transactions-table");
tbody.innerHTML = "";
transactions.forEach(txn => renderTransaction(txn));
}
// ATTACH form handler AFTER wallet is initialized
document.addEventListener("DOMContentLoaded", () => {
loadTransactions().then(() => {
const form = document.querySelector(".form-grid");
form.addEventListener("submit", async function(e) {
e.preventDefault();
if (!myWallet) return;
const amount = parseFloat(document.getElementById("amount").value);
const type = document.querySelector('input[name="type"]:checked').value;
// POST to server
const response = await fetch("https://mockapi.io/transactions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ date: new Date().toISOString().split("T")[0], type, amount })
});
const txn = await response.json();
myWallet.addTransaction(amount, type);
document.getElementById("wallet-balance").textContent = `₹ ${myWallet.balance.toFixed(2)}`;
renderTransaction(txn);
form.reset();
});
});
});// login.js — find user in MockAPI
document.getElementById("login-button").addEventListener("click", async () => {
const firstName = document.getElementById("first-name").value.trim();
const lastName = document.getElementById("last-name").value.trim();
const password = document.getElementById("password").value.trim();
const response = await fetch("https://mockapi.io/users");
const users = await response.json();
const user = users.find(u =>
u.first_name === firstName &&
u.last_name === lastName &&
u.password === password
);
if (user) {
localStorage.setItem("currentUser", JSON.stringify(user));
window.location.href = "dashboard.html";
} else {
alert("Invalid credentials.");
}
});
// register.js — POST new user to MockAPI
document.getElementById("register-form").addEventListener("submit", async (e) => {
e.preventDefault();
const newUser = {
first_name: document.getElementById("first-name").value.trim(),
last_name: document.getElementById("last-name").value.trim(),
password: document.getElementById("password").value.trim(),
account_type: document.getElementById("account-type").value
};
await fetch("https://mockapi.io/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newUser)
});
window.location.href = "login.html";
});| Error | Cause | Fix |
|---|---|---|
Cannot read properties of null |
getElementById returns null — wrong id or JS runs before HTML |
Check id matches exactly, move script to bottom of body |
CORS policy blocked |
Opening HTML with file:// — no server |
Use Live Server in VS Code or python -m http.server |
text is not defined |
Missing quotes around class names in template literal | Use single quotes inside template literals |
fetch failed ENOTFOUND |
Placeholder URL like api.example.com doesn't exist |
Use jsonplaceholder.typicode.com or MockAPI |
Cannot read balance of undefined |
localStorage.setItem ran before async loadTransactions finished |
Move localStorage code inside the async function |
Variables & Types
↓
Operators & Functions
↓
Classes & Objects (Transaction, Wallet)
↓
DOM Manipulation (select, modify, create, remove)
↓
Events (click, submit, change, focus, blur, keydown, keyup, mouseover, mouseout)
↓
Event Delegation
↓
Async JS — Callbacks → Promises → Async/Await
↓
Fetch API (GET, POST, PUT, DELETE)
↓
MockAPI — real server integration
↓
Login + Register with MockAPI
Each topic was first explained with a simple standalone example, then implemented directly into the wallet app — same approach as the Python journey.
Building on Assignment 1, this assignment covers everything from Classes → DOM → Events → Async → Fetch → MockAPI → Login/Register.
Task: Refactor your Assignment 1 code to use Transaction and Wallet classes.
Requirements:
Transaction class must have:
- constructor(amount, type, date)
- getDetails() method — returns formatted string
Wallet class must have:
- constructor(initialBalance)
- addTransaction(amount, type) — with validation
- getTotalCredits() — uses .filter() and .reduce()
- getTotalDebits() — uses .filter() and .reduce()
- getCashback() — 5% of credits if credits >= 500
What you need to implement:
// when form is submitted, create a Transaction object
// add it to Wallet
// update the balance card
// render the transaction in the list
// show total credits, debits, cashback below the list
// validate inside addTransaction:
// - amount must be a positive number
// - type must be "credit" or "debit"
// - debit cannot exceed current balanceExpected output in console:
Transaction added: CREDIT of ₹500 on 2026-04-23
New balance: ₹1945.50
Total Credits: ₹500
Total Debits: ₹0
Cashback Earned: ₹25
Task: Dynamically render everything using DOM methods — no hardcoded HTML in the transaction list.
Requirements — you MUST use each of these:
document.getElementById() // select balance card, stat cards
document.querySelector() // select form, checked radio
document.createElement() // create tr, td, span for each transaction
element.classList.add() // add text-green or text-red
element.classList.remove() // remove on reset
element.textContent // update balance, totals
element.innerHTML // build badge inside td
tbody.insertBefore(row, firstChild) // newest transaction at top
element.remove() // delete button removes a rowWhat to build:
Each transaction row must have:
- Date column
- Type column — badge (green for credit, red for debit)
- Amount column — colored + or − prefix
- Delete button — clicking removes that row and reverses the balance
Task: Wire up all 9 events from the requirements above plus these new ones specific to the wallet flow:
New events to add on top of Assignment 1:
change → when Credit/Debit dropdown changes:
- update a live preview label
- change the border color of amount input
(green for credit, red for debit)
keyup → as user types amount:
- show "You will receive ₹X after 2% fee" for credit
- show "You will pay ₹X including 2% fee" for debit
- update live every keystroke
focus → amount input:
- border turns blue
- show a hint text below: "Enter amount between ₹1 and ₹50,000"
blur → amount input:
- border resets
- hint text disappears
mouseover → each transaction row:
- background turns light yellow
- show a tooltip: "Click × to delete"
mouseout → each transaction row:
- background resets to white
Task: Simulate an async transaction — show a loading state while the transaction "processes".
Requirements:
// addTransactionAsync(amount, type) must:
// 1. return a Promise
// 2. show "Processing transaction..." text immediately
// 3. wait 1.5 seconds (simulate server delay using setTimeout)
// 4. resolve with the new Transaction object
// 5. reject if validation fails
// 6. hide loading text in finally block
// form handler must:
// - use async/await
// - disable the Add button while processing
// - change button text to "Processing..."
// - re-enable button after done
// - catch errors and show them to the userExpected UI behaviour:
User clicks Add
↓
Button → "Processing..." (disabled)
"Processing transaction..." text appears
↓
1.5 seconds later
↓
Balance updates
New row appears in table
Button → "Add Transaction" (enabled)
Loading text disappears
Task: Replace the simulated async with real MockAPI calls.
Setup steps:
1. Go to mockapi.io — create free account
2. Create project: "WalletAssignment"
3. Create resource: "transactions"
Fields: type (String), amount (Number), date (String)
4. Copy your base URL
Requirements:
// loadTransactions() must:
// - GET all transactions from MockAPI on page load
// - calculate balance, credits, debits from the response
// - render each transaction in the table
// - initialize Wallet with the fetched balance
// addTransaction form must:
// - POST new transaction to MockAPI
// - only render the row AFTER server confirms (response.ok)
// - show error message if POST fails
// delete button must:
// - DELETE from MockAPI using the transaction id
// - remove the row from table only AFTER server confirms
// - reverse the balance
// check response.status for every request:
// 200/201 → success
// 404 → not found
// 500 → server errorExpected flow:
Page loads
↓
GET /transactions → renders all existing transactions
↓
User adds ₹500 credit
↓
POST /transactions → server saves it → row appears
↓
User clicks delete on a row
↓
DELETE /transactions/:id → server deletes → row removed
Task: Build a complete login and register flow connected to MockAPI.
Setup:
Add another resource to your MockAPI project: "users"
Fields: first_name (String), last_name (String),
password (String), account_type (String),
balance (Number, default: 1000)
register.html requirements:
// form must collect:
// first_name, last_name, password, account_type
// on submit:
// 1. validate password — min 8 chars, has uppercase, lowercase, digit
// (use regex: /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$/)
// 2. POST to MockAPI /users
// 3. on success → alert "Registered! Please login" → redirect to login.html
// 4. on failure → show error message on page (not alert)login.html requirements:
// on submit:
// 1. GET all users from MockAPI
// 2. find user where first_name AND last_name AND password match
// 3. if found:
// - save user to localStorage: localStorage.setItem("currentUser", JSON.stringify(user))
// - redirect to dashboard.html
// 4. if not found:
// - show "Invalid credentials" error on page
// - shake the form (add a CSS class that animates it)dashboard.html requirements:
// on page load:
// 1. check localStorage for currentUser
// 2. if not found → redirect to login.html immediately
// 3. if found → show "Welcome back, {first_name}!" in the heading
// 4. show account type badge (Premium/Standard)
// 5. load transactions filtered by userId if you store itOnce all 6 parts work, try these:
1. PUT — edit a transaction amount inline
- clicking amount cell makes it editable (contenteditable)
- pressing Enter saves it — PUT to MockAPI
- pressing Escape cancels
2. Search — filter transactions live as you type
- input above the table
- keyup event filters rows by amount or type
- matching rows stay, non-matching rows get display:none
3. Sort — click column headers to sort
- clicking Date sorts by date asc/desc
- clicking Amount sorts by amount asc/desc
- show ▲ ▼ arrow in header to indicate direction
- implement using your bubble sort from the Python DSA sessions!
4. Logout button
- clears localStorage
- redirects to login.html
5. Session persistence
- on dashboard load, check if token is expired
- store login timestamp in localStorage
- if more than 30 minutes have passed → auto logout
wallet-assignment/
login.html ← Part 6
register.html ← Part 6
dashboard.html ← Parts 1-5
style.css ← your own CSS, no Bootstrap
js/
transaction.js ← Transaction class (Part 1)
wallet.js ← Wallet class (Part 1)
render.js ← renderTransaction() (Part 2)
events.js ← all event listeners (Part 3)
api.js ← all fetch calls (Part 5)
login.js ← login logic (Part 6)
register.js ← register logic (Part 6)
dashboard.js ← page init, DOMContentLoaded (Parts 4-5)
Part 1 — Classes
☐ Transaction class with constructor and getDetails()
☐ Wallet class with all 5 methods
☐ Validation inside addTransaction()
Part 2 — DOM
☐ All 9 DOM methods used
☐ Delete button reverses balance
☐ No hardcoded rows in HTML
Part 3 — Events
☐ All 9 events implemented
☐ Live fee preview on keyup
☐ Border color changes with transaction type
Part 4 — Async
☐ addTransactionAsync returns a Promise
☐ Button disabled during processing
☐ Loading text shows and hides correctly
Part 5 — Fetch
☐ GET loads transactions on page load
☐ POST saves new transaction
☐ DELETE removes transaction
☐ response.ok checked on every request
Part 6 — Login/Register
☐ Register POSTs to MockAPI with validation
☐ Login GETs and finds matching user
☐ currentUser saved to localStorage
☐ Dashboard redirects if not logged in
☐ Welcome message shows user's name