Masukkan Password
69 Mendesain Objek Pengeluaran
Lecture ini melanjutkan lecture sebelumnya. Kalau sebelumnya kita sudah belajar menampilkan expense ke UI, sekarang kita belajar mendesain tampilan setiap expense supaya tidak cuma berupa <h3>Salary</h3>, tetapi menjadi sebuah item yang memiliki:
- description
- amount
- tombol delete/cancel
- warna berbeda untuk
creditdandebit - layout yang lebih rapi menggunakan CSS
Jadi fokus utama lecture ini adalah:
Membuat HTML secara dinamis berdasarkan data
Expense, lalu menentukan CSS class berdasarkanexpense.type.
1. Kondisi sebelum lecture ini
Pada lecture sebelumnya, kita sudah punya function seperti:
function displayExpenseItems() {
debitDiv.innerHTML = "";
creditDiv.innerHTML = "";
for (let i = 0; i < expenseItems.length; i++) {
const expenseItem = expenseItems[i];
let containerDiv;
if (expenseItem.type === "credit") {
containerDiv = creditDiv;
} else {
containerDiv = debitDiv;
}
containerDiv.insertAdjacentHTML(
"beforeend",
`<h3>${expenseItem.description}</h3>`
);
}
}
Masalahnya, hasilnya masih sederhana:
Salary
Groceries
Internet
Padahal kita ingin tampilan seperti:
┌─────────────────────────────────────────────┐
│ Salary 10,000 X │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ Groceries 200 X │
└─────────────────────────────────────────────┘
Dan:
Credit → hijau
Debit → merah
2. Kita akan membuat HTML template
Daripada langsung menulis:
`<h3>${expenseItem.description}</h3>`
lecture membuat variable:
const template = `...`;
atau bisa juga:
let template = `...`;
Kenapa menggunakan backtick?
Karena kita ingin memasukkan data dari object expenseItem ke dalam HTML.
Misalnya:
const template = `
<div>
<div>${expenseItem.description}</div>
<div>${expenseItem.amount}</div>
</div>
`;
Kalau:
expenseItem.description = "Salary";
expenseItem.amount = 10000;
maka hasil akhirnya menjadi:
<div>
<div>Salary</div>
<div>10000</div>
</div>
3. Kenapa menggunakan template literal?
Kita sudah belajar ini sebelumnya.
Dengan string biasa:
"Hello ${name}"
${name} tidak akan diproses.
Tetapi dengan backtick:
`Hello ${name}`
TypeScript/JavaScript akan mengganti ${name} dengan nilainya.
Contoh:
const name = "John";
const message = `Hello ${name}`;
hasil:
Hello John
Dalam project kita:
`<div>${expenseItem.description}</div>`
akan memasukkan description expense ke dalam HTML.
4. Struktur HTML yang ingin dibuat
Lecture membuat satu parent <div>.
Di dalamnya terdapat tiga <div>:
<div class="...">
<div class="...">
description
</div>
<div class="...">
amount
</div>
<div class="...">
button
</div>
</div>
Jadi struktur sederhananya:
Parent Div
│
├── Description Div
│
├── Amount Div
│
└── Delete Button Div
Kalau dibuat visual:
┌──────────────────────────────────────┐
│ │
│ Description Amount X │
│ │
└──────────────────────────────────────┘
5. Kenapa ada tiga <div>?
Karena kita ingin memisahkan tiga informasi:
1. Description
2. Amount
3. Delete button
Misalnya expense:
Salary
10000
X
Maka:
<div>
<div>Salary</div>
<div>10000</div>
<div>
<button>X</button>
</div>
</div>
Dengan CSS, ketiganya bisa dibuat berada dalam satu baris.
6. Parent div menggunakan display: flex
Di CSS lecture sebelumnya sudah ada:
.credit-item,
.debit-item {
display: flex;
}
display: flex membuat child element disusun secara fleksibel, secara default dalam satu baris.
Tanpa Flexbox:
Salary
10000
X
Dengan Flexbox:
Salary 10000 X
Jadi kira-kira:
┌────────────────────────────────────┐
│ Salary 10000 X │
└────────────────────────────────────┘
7. CSS class berbeda untuk Credit dan Debit
Ini bagian yang sangat penting.
Kita sudah mempunyai CSS seperti:
.credit-item {
background: green;
}
.debit-item {
background: red;
}
Jadi kita ingin:
Expense type
│
├── credit → credit-item
│
└── debit → debit-item
Masalahnya:
Kita tidak bisa selalu menulis:
<div class="credit-item">
Karena kalau expense-nya debit, seharusnya:
<div class="debit-item">
Maka CSS class harus dibuat secara dinamis.
8. Membuat variable cssClass
Lecture membuat:
let cssClass;
Kemudian:
if (expenseItem.type === "credit") {
cssClass = "credit-item";
} else {
cssClass = "debit-item";
}
Ini sangat mirip dengan konsep containerDiv pada lecture sebelumnya.
Sebelumnya:
type
↓
tentukan container
Sekarang:
type
↓
tentukan CSS class
9. Contoh jika Expense adalah Credit
Misalnya:
expenseItem.type = "credit";
Maka:
if (expenseItem.type === "credit") {
cssClass = "credit-item";
}
hasil:
cssClass = "credit-item"
Kemudian HTML:
<div class="credit-item">
CSS .credit-item akan diterapkan.
Misalnya CSS-nya:
.credit-item {
background: green;
}
Maka tampilannya hijau.
10. Kalau Expense adalah Debit
Misalnya:
expenseItem.type = "debit";
Kondisi:
if (expenseItem.type === "credit")
bernilai false.
Maka:
cssClass = "debit-item";
HTML menjadi:
<div class="debit-item">
dan CSS:
.debit-item {
background: red;
}
akan diterapkan.
Jadi:
credit → green
debit → red
11. Dynamic CSS Class
Sekarang kita masukkan cssClass ke HTML.
Misalnya:
const template = `
<div class="${cssClass}">
...
</div>
`;
Perhatikan:
${cssClass}
Karena menggunakan template literal, variable tersebut akan diganti dengan nilainya.
Kalau:
cssClass = "credit-item";
maka hasil:
<div class="credit-item">
Kalau:
cssClass = "debit-item";
hasil:
<div class="debit-item">
Jadi kita membuat HTML yang dinamis.
12. Ini konsep yang sangat penting
Sebenarnya kita sedang melakukan:
DATA
↓
tentukan tampilan
↓
buat HTML
Contohnya:
expenseItem.type = "credit"
↓
cssClass
↓
"credit-item"
↓
<div class="credit-item">
Sedangkan:
expenseItem.type = "debit"
↓
cssClass
↓
"debit-item"
↓
<div class="debit-item">
Jadi data menentukan tampilan.
13. Menampilkan Description
Di div pertama:
<div class="expense-description">
${expenseItem.description}
</div>
Misalnya:
expenseItem.description = "Salary";
maka:
<div class="expense-description">
Salary
</div>
Kalau:
expenseItem.description = "Groceries"
maka:
<div class="expense-description">
Groceries
</div>
14. Menampilkan Amount
Div kedua digunakan untuk amount:
<div class="expense-amount">
${expenseItem.amount}
</div>
Misalnya:
expenseItem.amount = 10000;
hasil:
<div class="expense-amount">
10000
</div>
Jadi satu expense sekarang memiliki:
description
+
amount
15. Menambahkan tombol delete
Div ketiga berisi button:
<div class="expense-delete">
<button class="delete-expense">
X
</button>
</div>
Tujuannya nanti adalah:
User klik X
↓
expense dihapus
↓
UI diperbarui
Tetapi belum dilakukan di lecture ini.
Lecture ini baru membuat tombolnya.
Functionality delete akan dibuat kemudian.
16. Kenapa tombol diberi class?
Tombol diberi:
<button class="delete-expense">
Kenapa?
Supaya nanti kita bisa mencari button tersebut dari TypeScript.
Misalnya nanti:
document.querySelector(".delete-expense")
atau menggunakan event listener.
Selain itu CSS juga bisa menggunakan class tersebut.
Misalnya:
.delete-expense {
...
}
Jadi satu class dapat membantu:
CSS styling
+
JavaScript/TypeScript selection
17. Kenapa div delete juga diberi class?
Lecture juga memberikan class pada wrapper div:
<div class="expense-delete">
Ini memungkinkan kita mengatur posisi dan ukuran area tombol.
Misalnya CSS:
.expense-delete {
width: 10%;
text-align: center;
}
Maka area tombol bisa ditempatkan di bagian kanan.
Strukturnya menjadi:
┌───────────────────────────────────────────┐
│ Description Amount Delete│
│ │
└───────────────────────────────────────────┘
18. CSS width untuk Description
Lecture juga memiliki CSS class untuk description.
Misalnya:
.expense-description {
width: 65%;
}
Artinya area description mengambil sekitar 65% dari parent.
Misalnya:
Parent = 100%
Description = 65%
Amount = 25%
Delete = 10%
Total:
65% + 25% + 10% = 100%
Hasilnya kira-kira:
┌──────────────────────────────────────────┐
│ Description Amount X │
│<------ 65% ------> <25%> <10%> │
└──────────────────────────────────────────┘
19. CSS width untuk Amount
Untuk amount digunakan class:
.expense-amount {
width: 25%;
}
Jadi amount memiliki area tertentu.
Misalnya:
Description Amount Delete
65% 25% 10%
Hal ini membuat tampilan lebih rapi daripada membiarkan browser menentukan semuanya sendiri.
20. Kenapa menggunakan Flexbox + width?
Karena kita ingin membuat layout seperti tabel sederhana:
┌─────────────────────────────────────────────┐
│ Description Amount X │
├─────────────────────────────────────────────┤
│ Salary 10000 X │
│ Groceries 200 X │
│ Internet 500 X │
└─────────────────────────────────────────────┘
Flexbox menentukan bahwa element berada dalam satu baris.
Width menentukan berapa banyak ruang yang diberikan kepada masing-masing bagian.
21. Template lengkap secara konsep
Template yang dibuat lecture kira-kira seperti ini:
const template = `
<div class="${cssClass}">
<div class="expense-description">
${expenseItem.description}
</div>
<div class="expense-amount">
${expenseItem.amount}
</div>
<div class="expense-delete">
<button class="delete-expense">
X
</button>
</div>
</div>
`;
Perhatikan ada dua jenis variable dinamis:
CSS class
${cssClass}
Data expense
${expenseItem.description}
${expenseItem.amount}
Jadi template ini benar-benar dibuat berdasarkan data.
22. Setelah template dibuat, masukkan ke container
Sebelumnya kita punya:
containerDiv.insertAdjacentHTML(
"beforeend",
`<h3>${expenseItem.description}</h3>`
);
Sekarang diganti menjadi:
containerDiv.insertAdjacentHTML(
"beforeend",
template
);
Jadi alurnya:
expenseItem
↓
tentukan cssClass
↓
buat template HTML
↓
insertAdjacentHTML()
↓
DOM
↓
UI
23. Contoh lengkap dengan Salary
Misalnya:
expenseItem = {
type: "credit",
description: "Salary",
amount: 10000
};
Pertama:
expenseItem.type === "credit"
maka:
cssClass = "credit-item";
Template menjadi secara efektif:
<div class="credit-item">
<div class="expense-description">
Salary
</div>
<div class="expense-amount">
10000
</div>
<div class="expense-delete">
<button class="delete-expense">
X
</button>
</div>
</div>
CSS kemudian membuatnya terlihat sebagai credit.
24. Contoh dengan Groceries
Misalnya:
expenseItem = {
type: "debit",
description: "Groceries",
amount: 200
};
Karena:
expenseItem.type === "credit"
false, maka:
cssClass = "debit-item";
Template menjadi:
<div class="debit-item">
<div class="expense-description">
Groceries
</div>
<div class="expense-amount">
200
</div>
<div class="expense-delete">
<button class="delete-expense">
X
</button>
</div>
</div>
CSS .debit-item membuat background-nya merah.
25. Jadi satu function sekarang melakukan beberapa pekerjaan
displayExpenseItems() sekarang kira-kira melakukan:
1. Clear UI
↓
2. Loop expenseItems
↓
3. Ambil satu expense
↓
4. Tentukan credit/debit
↓
5. Tentukan container
↓
6. Tentukan CSS class
↓
7. Buat HTML template
↓
8. Masukkan HTML ke DOM
Visualnya:
expenseItems
│
↓
for loop
│
↓
expenseItem
│
┌───────┴────────┐
↓ ↓
type? data
↓ ↓
credit / debit description
↓ amount
↓ │
cssClass │
└───────┬────────┘
↓
HTML template
↓
insertAdjacentHTML()
↓
DOM
↓
UI
26. Hubungan containerDiv dan cssClass
Ada dua keputusan yang berbeda.
Pertama: masuk ke container mana?
if (expenseItem.type === "credit") {
containerDiv = creditDiv;
} else {
containerDiv = debitDiv;
}
Ini menentukan lokasi.
credit → kanan
debit → kiri
Kedua: menggunakan CSS class apa?
if (expenseItem.type === "credit") {
cssClass = "credit-item";
} else {
cssClass = "debit-item";
}
Ini menentukan tampilan/style.
credit → hijau
debit → merah
Jadi:
type
├── menentukan WHERE → containerDiv
│
└── menentukan HOW IT LOOKS → cssClass
Ini konsep yang bagus untuk dipahami.
27. Data yang sama digunakan untuk menentukan dua hal
Satu property:
expenseItem.type
digunakan untuk dua keputusan:
type
│
┌───────┴────────┐
↓ ↓
containerDiv cssClass
↓ ↓
creditDiv/debitDiv credit-item/debit-item
Contoh:
type = credit
↓
creditDiv
+
credit-item
Sedangkan:
type = debit
↓
debitDiv
+
debit-item
28. Kenapa ini disebut dynamic HTML?
Karena HTML yang dibuat tidak selalu sama.
Misalnya expense pertama:
credit + Salary + 10000
menghasilkan:
<div class="credit-item">
...
Salary
10000
...
</div>
Expense kedua:
debit + Groceries + 200
menghasilkan:
<div class="debit-item">
...
Groceries
200
...
</div>
HTML template-nya berasal dari data.
Jadi:
Data menentukan HTML yang dibuat.
29. Connection dengan konsep TypeScript sebelumnya
Lecture ini sebenarnya menggabungkan banyak konsep yang sudah kita pelajari.
Class
class Expense
digunakan untuk membuat object expense.
Literal Union
type: "credit" | "debit"
digunakan untuk menentukan jenis expense.
Array
Expense[]
menyimpan semua expense.
Loop
for (...)
memproses semua expense.
Type Assertion
as HTMLDivElement
digunakan saat mengambil element DOM.
Template Literal
`<div>${...}</div>`
digunakan untuk membuat HTML dinamis.
DOM manipulation
insertAdjacentHTML()
digunakan untuk memasukkan HTML ke halaman.
Jadi project ini mulai memperlihatkan bagaimana semua teori sebelumnya saling terhubung.
30. Hal menarik: kita sebenarnya sedang membuat “component”
Walaupun belum menggunakan React, Vue, atau framework lain, secara konsep kita sedang membuat sebuah UI component.
Satu Expense:
Expense object
↓
Expense HTML
↓
Expense UI
Misalnya:
Expense
{
type: "credit",
description: "Salary",
amount: 10000
}
menjadi:
┌───────────────────────────────┐
│ Salary 10000 X │
└───────────────────────────────┘
Jadi kita bisa berpikir:
Satu object Expense → satu visual expense item.
Ini adalah pola yang sangat sering digunakan dalam frontend modern.
31. Kenapa lecture sengaja membuat CSS sederhana?
Instruktur mengatakan bahwa tujuan project ini bukan membuat CSS yang sangat kompleks.
Tujuannya adalah memahami TypeScript.
Jadi styling-nya cukup untuk menunjukkan perbedaan:
Credit → green
Debit → red
dan layout:
Description | Amount | Delete
Yang penting adalah kita memahami proses:
TypeScript data
↓
HTML template
↓
CSS class
↓
UI
Bukan menghabiskan waktu untuk membuat desain yang rumit.
32. Bagian yang belum selesai: Delete button
Sekarang kita sudah membuat:
<button class="delete-expense">X</button>
Tetapi tombol tersebut belum melakukan apa-apa.
Kalau user klik:
X
belum ada logic:
hapus expense dari array
Itu kemungkinan akan dibahas di lecture berikutnya.
Nantinya kira-kira flow-nya:
User klik X
↓
cari expense yang sesuai
↓
hapus dari expenseItems
↓
displayExpenseItems()
↓
UI diperbarui
Dan ini akan menggunakan id yang sudah kita buat pada lecture sebelumnya.
33. Kenapa id Expense penting untuk delete?
Ingat class kita:
class Expense {
readonly id: number;
private static currentId = 0;
...
}
Setiap expense punya ID unik:
Salary → id 1
Groceries → id 2
Internet → id 3
Bonus → id 4
Nanti tombol delete perlu tahu:
“Tombol X ini milik Expense ID berapa?”
Sehingga kemungkinan besar nanti HTML akan menggunakan id tersebut sebagai penanda.
Misalnya konsepnya:
<button data-id="2">
X
</button>
Lalu TypeScript bisa mencari Expense dengan ID 2.
Jadi keputusan lecture sebelumnya membuat:
readonly id
sekarang mulai terasa manfaatnya.
34. Data flow project sekarang
Kita bisa melihat perkembangan project seperti ini:
FORM
│
↓
User memasukkan data
│
↓
new Expense(...)
│
↓
Expense Object
│
↓
expenseItems.push()
│
↓
Expense[] Array
│
↓
displayExpenseItems()
│
↓
┌─────┴─────┐
↓ ↓
Credit Debit
↓ ↓
creditDiv debitDiv
│ │
└─────┬─────┘
↓
HTML Template
↓
CSS class dynamic
↓
DOM
↓
UI
35. Hal yang paling penting untuk dipahami
Jangan hanya menghafal:
insertAdjacentHTML()
atau:
innerHTML = "";
Yang lebih penting adalah memahami cara berpikirnya.
Kita memiliki data:
expenseItems
Data tersebut adalah sumber informasi.
Kemudian kita mengubah data menjadi UI:
DATA
↓
RENDER
↓
UI
Dan ketika data berubah:
DATA BERUBAH
↓
RENDER ULANG
↓
UI TERBARU
Itulah pola dasar yang sangat penting dalam frontend.
36. Ringkasan setiap bagian code
| Code | Artinya |
|---|---|
`...` | template literal |
${expenseItem.description} | masukkan description ke HTML |
${expenseItem.amount} | masukkan amount ke HTML |
cssClass | menyimpan nama CSS class secara dinamis |
"credit-item" | style untuk credit |
"debit-item" | style untuk debit |
containerDiv | menentukan container tempat expense ditampilkan |
insertAdjacentHTML() | memasukkan HTML ke DOM |
"beforeend" | masukkan HTML di bagian akhir |
display: flex | menyusun child dalam satu baris |
.expense-description | styling area description |
.expense-amount | styling area amount |
.expense-delete | styling area tombol delete |
.delete-expense | styling/selector tombol delete |
37. Cheat sheet paling sederhana
Ingat pola berikut:
expenseItem
│
├── type
│ ├── credit
│ │ ↓
│ │ creditDiv
│ │ +
│ │ credit-item
│ │
│ └── debit
│ ↓
│ debitDiv
│ +
│ debit-item
│
├── description
│ ↓
│ HTML description
│
└── amount
↓
HTML amount
Kemudian semuanya digabung:
expenseItem
↓
HTML Template
↓
insertAdjacentHTML()
↓
UI
38. Inti lecture dalam satu kalimat
Kita sekarang mengubah setiap
Expenseobject menjadi HTML yang lebih lengkap, menggunakanexpenseItem.typeuntuk menentukan container dan CSS class, lalu memasukkan description, amount, dan tombol delete ke dalam UI menggunakan template literal daninsertAdjacentHTML().
Kalau lecture sebelumnya fokus pada:
“Bagaimana menampilkan data?”
maka lecture ini fokus pada:
“Bagaimana membuat data tersebut tampil dengan bentuk dan style yang sesuai?”
Dan pola yang perlu kamu ingat adalah:
DATA
↓
CHECK TYPE
↓
CHOOSE CONTAINER
↓
CHOOSE CSS CLASS
↓
CREATE TEMPLATE
↓
INSERT INTO DOM
↓
UI
Ini adalah salah satu pola dasar yang nanti akan terus kamu temui ketika belajar frontend development lebih lanjut.