Daily Work Note
Saved Work Notes
| Date | Task Title | Details | Action |
|---|
Dưới đây là bài hướng dẫn chi tiết, dễ hiểu dành cho học sinh, sinh viên để xây dựng một ứng dụng Daily Work Note (Ghi chú công việc hàng ngày).
Ứng dụng này sử dụng HTML Form để nhập liệu, hiển thị dạng Bảng (Table), và lưu trữ dữ liệu bền vững ngay trên trình duyệt bằng IndexedDB (không bị mất khi F5 hoặc tắt máy). Mã nguồn được tối ưu hóa gọn gàng trong 1 file để bạn dễ dàng nhúng thẳng vào Blogspot.
1. Tìm hiểu nhanh về các thành phần
HTML Form: Nơi người dùng nhập tiêu đề công việc, nội dung và chọn ngày tháng.
HTML Table: Nơi hiển thị danh sách các ghi chú đã lưu dưới dạng các hàng và cột trực quan.
IndexedDB: Một cơ sở dữ liệu (Database) thực sự tích hợp sẵn trong trình duyệt web. Nó mạnh hơn
localStoragevì cho phép lưu trữ cấu trúc dữ liệu lớn, tìm kiếm và quản lý theo chỉ mục (Index).
2. Mã nguồn toàn diện (HTML + CSS + JavaScript)
Bạn chỉ cần sao chép toàn bộ đoạn code dưới đây. Code đã được viết theo chuẩn gọn - nhẹ - không dùng thư viện ngoài để tải nhanh nhất trên Blogspot.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Daily Work Note - IndexedDB</title>
<style>
:root {
--primary-color: #2563eb;
--danger-color: #dc2626;
--bg-color: #f8fafc;
--text-color: #1e293b;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
background: #ffffff;
padding: 25px;
border-radius: 8px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
h2 {
margin-top: 0;
color: var(--primary-color);
border-bottom: 2px solid #e2e8f0;
padding-bottom: 10px;
}
/* Style for Form */
.note-form {
display: grid;
gap: 15px;
margin-bottom: 30px;
background: #f1f5f9;
padding: 15px;
border-radius: 6px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 5px;
}
label {
font-weight: 600;
font-size: 0.9rem;
}
input, textarea {
padding: 8px 12px;
border: 1px solid #cbd5e1;
border-radius: 4px;
font-size: 1rem;
outline: none;
}
input:focus, textarea:focus {
border-color: var(--primary-color);
}
button {
cursor: pointer;
padding: 10px 15px;
border: none;
border-radius: 4px;
font-weight: bold;
transition: opacity 0.2s;
}
button:hover {
opacity: 0.9;
}
.btn-submit {
background-color: var(--primary-color);
color: white;
align-self: flex-start;
}
/* Style for Data Table */
.table-container {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
text-align: left;
}
th, td {
padding: 12px;
border-bottom: 1px solid #e2e8f0;
}
th {
background-color: #e2e8f0;
font-weight: 600;
}
tr:hover {
background-color: #f8fafc;
}
.btn-delete {
background-color: var(--danger-color);
color: white;
padding: 5px 10px;
font-size: 0.85rem;
}
.no-data {
text-align: center;
color: #64748b;
padding: 20px;
}
</style>
</head>
<body>
<div class="container">
<h2>Daily Work Note</h2>
<form id="workNoteForm" class="note-form">
<div class="form-group">
<label for="noteDate">Date:</label>
<input type="date" id="noteDate" required>
</div>
<div class="form-group">
<label for="noteTitle">Task Title:</label>
<input type="text" id="noteTitle" placeholder="What are you doing today?..." required>
</div>
<div class="form-group">
<label for="noteContent">Details/Content:</label>
<textarea id="noteContent" rows="3" placeholder="Enter task descriptions, targets..."></textarea>
</div>
<button type="submit" class="btn-submit">Save Note</button>
</form>
<h3>Saved Work Notes</h3>
<div class="table-container">
<table id="notesTable">
<thead>
<tr>
<th>Date</th>
<th>Task Title</th>
<th>Details</th>
<th style="width: 80px;">Action</th>
</tr>
</thead>
<tbody id="notesTableBody">
</tbody>
</table>
<div id="noDataMessage" class="no-data" style="display: none;">No notes recorded yet.</div>
</div>
</div>
<script>
// --- 1. INITIALIZE INDEXEDDB ---
const dbName = "DailyWorkNoteDB";
const dbVersion = 1;
const storeName = "notes";
let db;
const request = indexedDB.open(dbName, dbVersion);
// Triggered if the database is new or version changes
request.onupgradeneeded = function(event) {
const database = event.target.result;
// Create an objectStore (table) with an auto-incrementing key path named 'id'
if (!database.objectStoreNames.contains(storeName)) {
database.createObjectStore(storeName, { keyPath: "id", autoIncrement: true });
}
};
request.onsuccess = function(event) {
db = event.target.result;
console.log("Database initialized successfully.");
displayNotes(); // Load data onto the table upon opening
};
request.onerror = function(event) {
console.error("Database error:", event.target.error);
};
// Set default date picker to today's date
document.getElementById('noteDate').valueAsDate = new Date();
// --- 2. INSERT DATA (CREATE) ---
const form = document.getElementById('workNoteForm');
form.addEventListener('submit', function(e) {
e.preventDefault();
const date = document.getElementById('noteDate').value;
const title = document.getElementById('noteTitle').value;
const content = document.getElementById('noteContent').value;
const newNote = {
date: date,
title: title,
content: content,
createdAt: new Date().getTime()
};
const transaction = db.transaction([storeName], "readwrite");
const store = transaction.objectStore(storeName);
const addRequest = store.add(newNote);
addRequest.onsuccess = function() {
form.reset();
document.getElementById('noteDate').valueAsDate = new Date(); // Re-set date
displayNotes(); // Refresh data table
};
transaction.oncomplete = function() {
console.log("Transaction completed: Note added.");
};
});
// --- 3. FETCH & DISPLAY DATA (READ) ---
function displayNotes() {
const tableBody = document.getElementById('notesTableBody');
const noDataMessage = document.getElementById('noDataMessage');
tableBody.innerHTML = ""; // Clear existing rows
const transaction = db.transaction([storeName], "readonly");
const store = transaction.objectStore(storeName);
// Open a cursor to iterate through records (sorted by ID)
const cursorRequest = store.openCursor(null, "prev"); // "prev" to see newest first
let hasData = false;
cursorRequest.onsuccess = function(event) {
const cursor = event.target.result;
if (cursor) {
hasData = true;
const note = cursor.value;
const row = document.createElement('tr');
row.innerHTML = `
<td>${formatDate(note.date)}</td>
<td><strong>${escapeHTML(note.title)}</strong></td>
<td>${escapeHTML(note.content).replace(/\n/g, '<br>')}</td>
<td><button class="btn-delete" onclick="deleteNote(${note.id})">Delete</button></td>
`;
tableBody.appendChild(row);
cursor.continue(); // Move to the next record
} else {
// Executed when cursor finished iterating
if (!hasData) {
noDataMessage.style.display = "block";
} else {
noDataMessage.style.display = "none";
}
}
};
}
// --- 4. DELETE DATA (DELETE) ---
window.deleteNote = function(id) {
if (confirm("Are you sure you want to delete this note?")) {
const transaction = db.transaction([storeName], "readwrite");
const store = transaction.objectStore(storeName);
const deleteRequest = store.delete(id);
deleteRequest.onsuccess = function() {
console.log(`Note with ID ${id} deleted.`);
displayNotes(); // Refresh table UI
};
}
};
// --- HELPER FUNCTIONS ---
function formatDate(dateString) {
if (!dateString) return "";
const parts = dateString.split("-");
if (parts.length === 3) {
return `${parts[2]}/${parts[1]}/${parts[0]}`; // Convert YYYY-MM-DD to DD/MM/YYYY
}
return dateString;
}
function escapeHTML(str) {
return str.replace(/[&<>'"]/g,
tag => ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[tag] || tag)
);
}
</script>
</body>
</html>
3. Các bước nhúng vào Blogspot an toàn
Để mã nguồn này chạy hoàn hảo trên Blogger/Blogspot mà không bị xung đột cú pháp, bạn thực hiện theo các bước sau:
Bước 1: Tạo bài viết hoặc trang mới
Đăng nhập vào trang quản trị Blogger.
Chọn Bài đăng mới (New Post) hoặc Trang (Pages) -> Trang mới.
Bước 2: Chuyển sang chế độ xem HTML
Tại thanh công cụ soạn thảo, nhấp vào biểu tượng Cây bút/Thước kẻ ở góc trên cùng bên trái.
Chọn Chế độ xem HTML (HTML view). Xóa mọi đoạn code mặc định đang có sẵn đi.
Bước 3: Dán mã nguồn và xử lý ký tự (Quan trọng)
Blogger đôi khi tự động định dạng lại mã JavaScript khi lưu, dẫn đến lỗi cú pháp (đặc biệt là các ký tự logic như && hoặc <). Đoạn code trên đã được tối ưu hóa bằng hàm tránh xung đột, nhưng để an toàn nhất:
Dán toàn bộ mã nguồn ở mục (2) vào ô soạn thảo HTML.
Nhấp Xuất bản (Publish).
4. Kiến thức cốt lõi cần nhớ cho học sinh, sinh viên
Khi học đoạn code trên, bạn hãy chú ý vào luồng xử lý dữ liệu (CRUD cơ bản):
Mở kết nối: Dùng
indexedDB.open(). Nếu DB chưa có, hàmonupgradeneededsẽ chạy để tạo một cấu trúc bảng (objectStore) có khóa tự tăng (autoIncrement: true).Transaction (Giao dịch): Mọi thao tác ghi (
readwrite) hoặc đọc (readonly) trong IndexedDB đều bắt buộc phải nằm trong một Transaction để đảm bảo tính toàn vẹn dữ liệu (nếu lỗi, nó tự động rollback để không làm hỏng database).Cursor (Con trỏ): Vì IndexedDB không dùng câu lệnh
SELECT *, chúng ta dùngopenCursor()để duyệt qua từng hàng dữ liệu từ trên xuống dưới (hoặc ngược lại với"prev") rồi render ra HTML Table bằng JS DOM (appendChild).



0 Reviews