
Fondasi yang Kokoh: HTML untuk Struktur
Sebelum kita mulai bermain dengan warna dan gaya, mari kita siapkan "kerangka" dari kedua aplikasi kita. HTML (HyperText Markup Language) adalah tulang punggung dari setiap halaman web. Tanpanya, kita hanya akan melihat teks polos tanpa bentuk.Kalkulator Sederhana: Membangun Antarmuka Numerik
Untuk kalkulator, kita membutuhkan area tampilan untuk angka dan hasil, serta tombol-tombol angka (0-9), operator (+, -, *, /), tombol sama dengan (=), dan tombol hapus (C). ```htmlPage Builder: Komponen untuk Membangun Halaman
Untuk page builder, strukturnya akan sedikit berbeda. Kita memerlukan area kerja utama (canvas) tempat pengguna akan membangun halaman, dan sebuah panel samping (sidebar) yang berisi berbagai komponen yang bisa ditambahkan, seperti teks, gambar, tombol, dan kolom. ```htmlSentuhan Artistik: CSS untuk Estetika
Setelah kerangka terbangun, saatnya kita mempercantik tampilan. CSS (Cascading Style Sheets) adalah "salon kecantikan" untuk web Anda. Tanpa CSS, halaman web kita akan terlihat seperti resep masakan yang hanya berisi daftar bahan tanpa instruksi memasak yang rapi.Kalkulator: Fungsional dan Menarik
Kita ingin kalkulator kita terlihat bersih, modern, dan mudah digunakan. ```css .calculator { width: 320px; margin: 50px auto; border: 1px solid #ccc; border-radius: 10px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); overflow: hidden; } .calculator-display { width: calc(100% - 20px); padding: 10px; background-color: #222; color: white; font-size: 2em; text-align: right; border: none; outline: none; } .calculator-keys { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; /* Membuat garis pemisah antar tombol */ background-color: #ccc; } .calculator-keys button { padding: 20px; font-size: 1.2em; border: none; background-color: #f9f9f9; cursor: pointer; transition: background-color 0.3s ease; } .calculator-keys button:hover { background-color: #ddd; } .calculator-keys button.operator { background-color: #f0ad4e; color: white; } .calculator-keys button.operator:hover { background-color: #ec971f; } .calculator-keys button.equal-sign { background-color: #5cb85c; color: white; grid-column: span 2; /* Tombol '=' mencakup 2 kolom */ } .calculator-keys button.equal-sign:hover { background-color: #4cae4c; } .calculator-keys button[data-action="clear"] { background-color: #d9534f; color: white; } .calculator-keys button[data-action="clear"]:hover { background-color: #c9302c; } ``` Dengan menggunakan CSS Grid, kita bisa dengan mudah mengatur tata letak tombol kalkulator agar menyerupai kalkulator fisik.Page Builder: UI yang Intuitif
Untuk page builder, kita ingin antarmuka yang bersih, modern, dan responsif. Panel samping harus jelas, dan area kerja harus memberikan ruang yang cukup untuk berkreasi. ```css .page-builder-container { display: flex; height: 100vh; /* Mengisi seluruh tinggi viewport */ } .sidebar { width: 250px; background-color: #f4f7f6; padding: 20px; box-shadow: 2px 0 10px rgba(0, 0, 0, 0.05); overflow-y: auto; } .sidebar h3 { margin-top: 0; color: #333; border-bottom: 1px solid #e0e0e0; padding-bottom: 10px; } .component-item { background-color: #fff; border: 1px solid #e0e0e0; border-radius: 5px; padding: 15px; margin-bottom: 10px; cursor: grab; /* Indikator bahwa elemen bisa diseret */ display: flex; align-items: center; gap: 10px; transition: box-shadow 0.3s ease; } .component-item:hover { box-shadow: 0 0 15px rgba(0, 0, 0, 0.1); } .component-item i { color: #5a5a5a; font-size: 1.2em; } .canvas { flex-grow: 1; /* Mengambil sisa ruang */ padding: 30px; background-color: #fdfdfd; overflow: auto; /* Scroll jika konten melebihi area */ position: relative; /* Penting untuk menempatkan elemen di dalamnya */ } /* Styling untuk elemen yang diletakkan di canvas (akan ditambahkan oleh JS) */ .canvas .component-element { border: 1px dashed #ccc; padding: 15px; margin-bottom: 15px; background-color: #fff; cursor: move; /* Indikator bisa dipindah */ position: absolute; /* Agar bisa diposisikan bebas */ } .canvas .component-element.text-element { width: 100%; /* Default width for text */ height: auto; } .canvas .component-element.image-element { width: 200px; /* Default width for image */ height: 150px; background-color: #e0e0e0; /* Placeholder background */ } .canvas .component-element.button-element { width: auto; height: auto; padding: 10px 20px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; } .canvas .component-element.columns-element { width: 100%; height: 100px; display: flex; gap: 10px; background-color: #e9ecef; border: 1px dashed #adb5bd; } .canvas .component-element.columns-element div { flex: 1; background-color: #dee2e6; border: 1px dashed #ced4da; } ``` Di sini, kita menggunakan Flexbox untuk mengatur tata letak utama (`page-builder-container`). CSS Grid tidak digunakan secara langsung untuk tata letak utama, namun bisa saja digunakan di dalam elemen komponen tertentu, seperti kolom.Otak di Balik Aksi: JavaScript untuk Interaktivitas
Inilah saatnya membuat kedua aplikasi kita "hidup". JavaScript adalah "mesin" yang menjalankan semua fungsi. Tanpa JavaScript, kalkulator kita hanya tumpukan tombol tanpa fungsi, dan page builder kita hanya tampilan statis tanpa kemampuan membangun.Kalkulator: Logika Perhitungan
Kita perlu mendengarkan klik pada setiap tombol, mengambil input angka dan operator, melakukan perhitungan, dan menampilkan hasilnya. ```javascript document.addEventListener('DOMContentLoaded', () => { const display = document.querySelector('.calculator-display'); const keys = document.querySelector('.calculator-keys'); let displayedNum = ''; let firstOperand = null; let operator = null; let waitingForSecondOperand = false; keys.addEventListener('click', (e) => { const target = e.target; if (!target.matches('button')) { return; } const action = target.dataset.action; const keyContent = target.textContent; if (action === 'clear') { displayedNum = ''; firstOperand = null; operator = null; waitingForSecondOperand = false; display.value = ''; return; } if (target.classList.contains('operator')) { const inputValue = parseFloat(displayedNum); if (operator && waitingForSecondOperand) { // Jika sudah ada operator dan sedang menunggu operan kedua, lakukan kalkulasi langsung operator = action; return; } if (firstOperand === null) { firstOperand = inputValue; } else if (operator) { const result = calculate(firstOperand, inputValue, operator); display.value = result; firstOperand = result; } waitingForSecondOperand = true; operator = action; displayedNum = ''; // Reset displayedNum untuk input operan kedua return; } if (action === 'decimal') { if (!displayedNum.includes('.')) { displayedNum += '.'; } return; } if (action === 'calculate') { if (firstOperand === null || operator === null) { return; // Tidak bisa menghitung jika belum ada operan atau operator } const secondOperand = parseFloat(displayedNum); const result = calculate(firstOperand, secondOperand, operator); display.value = result; firstOperand = result; operator = null; waitingForSecondOperand = false; displayedNum = String(result); // Hasil menjadi basis untuk perhitungan selanjutnya return; } // Input angka if (waitingForSecondOperand) { displayedNum = keyContent; waitingForSecondOperand = false; } else { displayedNum = displayedNum + keyContent; } display.value = displayedNum; }); function calculate(num1, num2, operator) { if (operator === 'add') return num1 + num2; if (operator === 'subtract') return num1 - num2; if (operator === 'multiply') return num1 * num2; if (operator === 'divide') { if (num2 === 0) return 'Error'; // Pencegahan pembagian dengan nol return num1 / num2; } return undefined; } }); ``` Logika kalkulator ini cukup detail. Kita menyimpan `firstOperand`, `operator`, dan status `waitingForSecondOperand` untuk mengelola alur perhitungan. Setiap tombol memiliki peran spesifik dalam mengubah status ini atau melakukan perhitungan. Ini seperti seorang koki yang mencatat bahan apa saja yang sudah masuk, urutan memasak, dan menunggu bahan berikutnya sebelum melanjutkan ke tahap selanjutnya.Page Builder: Drag and Drop dan Penambahan Komponen
Untuk page builder, kita perlu mengimplementasikan fungsionalitas drag and drop dari sidebar ke canvas, dan kemudian menambahkan elemen HTML yang sesuai ke canvas. ```javascript document.addEventListener('DOMContentLoaded', () => { const components = document.querySelectorAll('.component-item'); const canvas = document.getElementById('pageCanvas'); let draggedItem = null; // Menyimpan elemen yang sedang diseret // 1. Drag Start: Saat elemen di sidebar mulai diseret components.forEach(component => { component.addEventListener('dragstart', (e) => { draggedItem = component; e.dataTransfer.setData('text/plain', component.dataset.componentType); e.dataTransfer.effectAllowed = 'copy'; // Menandakan ini adalah operasi penyalinan setTimeout(() => { component.style.opacity = '0.5'; // Efek visual saat menyeret }, 0); }); // Mengembalikan opacity elemen setelah drag selesai component.addEventListener('dragend', () => { draggedItem.style.opacity = '1'; draggedItem = null; }); }); // 2. Drag Over: Saat elemen diseret di atas area target (canvas) canvas.addEventListener('dragover', (e) => { e.preventDefault(); // Penting: Mencegah perilaku default agar drop bisa dilakukan e.dataTransfer.dropEffect = 'copy'; // Menampilkan ikon 'copy' saat hover }); // 3. Drop: Saat elemen dilepaskan di area target (canvas) canvas.addEventListener('drop', (e) => { e.preventDefault(); const componentType = e.dataTransfer.getData('text/plain'); // Membuat elemen baru berdasarkan jenis komponen yang di-drop let newElement; switch (componentType) { case 'text': newElement = document.createElement('div'); newElement.classList.add('component-element', 'text-element'); newElement.innerHTML = 'Tulis teks Anda di sini...
'; break; case 'image': newElement = document.createElement('div'); newElement.classList.add('component-element', 'image-element'); newElement.innerHTML = '
'; // Gunakan gambar placeholder
break;
case 'button':
newElement = document.createElement('div');
newElement.classList.add('component-element', 'button-element');
newElement.innerHTML = '';
break;
case 'columns':
newElement = document.createElement('div');
newElement.classList.add('component-element', 'columns-element');
newElement.innerHTML = ''; // Dua kolom default
break;
default:
return;
}
// Menentukan posisi elemen baru di canvas
// Posisi relatif terhadap posisi kursor saat drop
const canvasRect = canvas.getBoundingClientRect();
const x = e.clientX - canvasRect.left - 100; // Kurangi setengah lebar perkiraan elemen
const y = e.clientY - canvasRect.top - 50; // Kurangi setengah tinggi perkiraan elemen
newElement.style.position = 'absolute';
newElement.style.left = `${x}px`;
newElement.style.top = `${y}px`;
canvas.appendChild(newElement);
// Membuat elemen yang baru ditambahkan bisa digerakkan (draggable)
makeDraggable(newElement);
});
// Fungsi untuk membuat elemen di canvas menjadi bisa digerakkan
function makeDraggable(element) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
// Fungsi untuk memindahkan elemen saat mouse bergerak
const elementDrag = (e) => {
e = e || window.event;
e.preventDefault();
pos3 = pos1 - e.clientX;
pos4 = pos2 - e.clientY;
pos1 = e.clientX;
pos2 = e.clientY;
// Tetapkan posisi baru elemen
element.style.top = (element.offsetTop - pos4) + "px";
element.style.left = (element.offsetLeft - pos3) + "px";
}
// Fungsi saat mouse dilepas (drag selesai)
const closeDragElement = () => {
document.onmouseup = null;
document.onmousemove = null;
element.style.cursor = 'move'; // Kembalikan kursor default
}
// Fungsi saat tombol mouse ditekan pada elemen
element.onmousedown = (e) => {
e = e || window.event;
e.preventDefault();
pos1 = e.clientX;
pos2 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
element.style.cursor = 'grabbing'; // Ubah kursor saat menyeret
}
}
});
```
Logika drag and drop ini memanfaatkan event `dragstart`, `dragover`, dan `drop`. Event `preventDefault()` sangat krusial pada `dragover` agar proses `drop` bisa berjalan. Fungsi `makeDraggable` adalah inti dari pemindahan elemen di dalam canvas, mengimplementasikan logika kalkulasi posisi saat mouse bergerak. Ini mirip seperti bagaimana seorang seniman memindahkan kanvasnya di studio untuk mendapatkan sudut pandang terbaik, atau bagaimana tukang memposisikan kusen pintu sebelum dipasang permanen.