Buổi 4: Thêm chức năng "Thêm công việc"
Loại buổi: Thực hành
Thời lượng: 120 phút
Dự án: To-Do App (đã có form nhập liệu từ buổi 2)
🎯 Mục tiêu học tập
Sau buổi học này, bạn sẽ có thể:
- ✅ Tạo hàm để thêm công việc
- ✅ Validate dữ liệu đầu vào
- ✅ Lưu công việc vào mảng
- ✅ Hiển thị danh sách công việc (tạm thời bằng alert/console)
- ✅ Áp dụng kiến thức hàm và điều kiện từ buổi 3
🧩 Task Project
Task 1: Tạo cấu trúc dữ liệu (15 phút)
Trong main.js, tạo mảng để lưu danh sách công việc:
javascript
// Mảng lưu trữ danh sách công việc
let danhSachCongViec = [];
// Cấu trúc một công việc
// {
// id: số duy nhất,
// ten: 'Tên công việc',
// moTa: 'Mô tả',
// trangThai: 'chua lam',
// ngayTao: '2024-01-01'
// }Task 2: Tạo hàm validate dữ liệu (20 phút)
javascript
/**
* Kiểm tra tên công việc có hợp lệ không
* @param {string} ten - Tên công việc
* @returns {boolean} true nếu hợp lệ, false nếu không
*/
function kiemTraTenCongViec(ten) {
// Kiểm tra rỗng
if (!ten || ten.trim().length === 0) {
return false;
}
// Kiểm tra độ dài (ít nhất 3 ký tự, tối đa 100 ký tự)
if (ten.trim().length < 3) {
return false;
}
if (ten.trim().length > 100) {
return false;
}
return true;
}Test hàm:
javascript
console.log(kiemTraTenCongViec('')); // false
console.log(kiemTraTenCongViec('Học')); // false (quá ngắn)
console.log(kiemTraTenCongViec('Học JavaScript')); // trueTask 3: Tạo hàm thêm công việc (30 phút)
javascript
/**
* Thêm công việc mới vào danh sách
* @param {string} ten - Tên công việc
* @param {string} moTa - Mô tả công việc
* @returns {Object|null} Công việc đã thêm hoặc null nếu lỗi
*/
function themCongViec(ten, moTa = '') {
// Bước 1: Validate dữ liệu
if (!kiemTraTenCongViec(ten)) {
alert('Tên công việc không hợp lệ!\n- Không được rỗng\n- Ít nhất 3 ký tự\n- Tối đa 100 ký tự');
return null;
}
// Bước 2: Tạo object công việc
const congViec = {
id: Date.now(), // Tạo ID duy nhất từ timestamp
ten: ten.trim(),
moTa: moTa.trim(),
trangThai: 'chua lam',
ngayTao: new Date().toISOString()
};
// Bước 3: Thêm vào mảng
danhSachCongViec.push(congViec);
// Bước 4: Thông báo thành công
console.log('Đã thêm công việc:', congViec);
alert(`Đã thêm công việc: ${congViec.ten}`);
// Bước 5: Trả về công việc đã thêm
return congViec;
}Task 4: Kết nối với form (20 phút)
Cập nhật event listener của form:
javascript
const form = document.getElementById('form-cong-viec');
form.addEventListener('submit', function(event) {
event.preventDefault();
// Lấy giá trị từ form
const tenCongViec = document.getElementById('ten-cong-viec').value;
const moTa = document.getElementById('mo-ta').value;
// Gọi hàm thêm công việc
const congViecMoi = themCongViec(tenCongViec, moTa);
// Nếu thêm thành công, reset form
if (congViecMoi) {
form.reset();
hienThiDanhSach(); // Hiển thị lại danh sách (sẽ tạo ở Task 5)
}
});Task 5: Tạo hàm hiển thị danh sách (25 phút)
javascript
/**
* Hiển thị danh sách công việc ra console
*/
function hienThiDanhSach() {
console.log('=== DANH SÁCH CÔNG VIỆC ===');
if (danhSachCongViec.length === 0) {
console.log('Danh sách trống!');
return;
}
// Dùng forEach để hiển thị từng công việc
danhSachCongViec.forEach(function(congViec, index) {
console.log(`${index + 1}. ${congViec.ten}`);
console.log(` Mô tả: ${congViec.moTa || 'Không có'}`);
console.log(` Trạng thái: ${congViec.trangThai}`);
console.log(` Ngày tạo: ${new Date(congViec.ngayTao).toLocaleString('vi-VN')}`);
console.log('---');
});
console.log(`Tổng cộng: ${danhSachCongViec.length} công việc`);
}
// Hiển thị danh sách khi trang load
hienThiDanhSach();Cải thiện với console.table():
javascript
function hienThiDanhSach() {
if (danhSachCongViec.length === 0) {
console.log('Danh sách trống!');
return;
}
// Hiển thị dạng bảng
console.table(danhSachCongViec.map((cv, index) => ({
'STT': index + 1,
'Tên': cv.ten,
'Mô tả': cv.moTa || 'Không có',
'Trạng thái': cv.trangThai,
'Ngày tạo': new Date(cv.ngayTao).toLocaleDateString('vi-VN')
})));
}Task 6: Thêm tính năng đếm công việc (10 phút)
javascript
/**
* Đếm số lượng công việc theo trạng thái
* @param {string} trangThai - Trạng thái cần đếm (optional)
* @returns {number} Số lượng công việc
*/
function demCongViec(trangThai = null) {
if (trangThai === null) {
return danhSachCongViec.length;
}
return danhSachCongViec.filter(function(cv) {
return cv.trangThai === trangThai;
}).length;
}
// Sử dụng
console.log('Tổng số công việc:', demCongViec());
console.log('Chưa làm:', demCongViec('chua lam'));Hiển thị thống kê:
javascript
function hienThiThongKe() {
const tong = demCongViec();
const chuaLam = demCongViec('chua lam');
const dangLam = demCongViec('dang lam');
const hoanThanh = demCongViec('hoan thanh');
console.log('=== THỐNG KÊ ===');
console.log(`Tổng số: ${tong}`);
console.log(`Chưa làm: ${chuaLam}`);
console.log(`Đang làm: ${dangLam}`);
console.log(`Hoàn thành: ${hoanThanh}`);
}💻 Code hoàn chỉnh
javascript
// ===== DỮ LIỆU =====
let danhSachCongViec = [];
// ===== VALIDATION =====
function kiemTraTenCongViec(ten) {
if (!ten || ten.trim().length === 0) return false;
if (ten.trim().length < 3) return false;
if (ten.trim().length > 100) return false;
return true;
}
// ===== THÊM CÔNG VIỆC =====
function themCongViec(ten, moTa = '') {
if (!kiemTraTenCongViec(ten)) {
alert('Tên công việc không hợp lệ!');
return null;
}
const congViec = {
id: Date.now(),
ten: ten.trim(),
moTa: moTa.trim(),
trangThai: 'chua lam',
ngayTao: new Date().toISOString()
};
danhSachCongViec.push(congViec);
console.log('Đã thêm:', congViec);
return congViec;
}
// ===== HIỂN THỊ =====
function hienThiDanhSach() {
if (danhSachCongViec.length === 0) {
console.log('Danh sách trống!');
return;
}
console.table(danhSachCongViec.map((cv, i) => ({
'STT': i + 1,
'Tên': cv.ten,
'Mô tả': cv.moTa || 'Không có',
'Trạng thái': cv.trangThai
})));
}
// ===== ĐẾM =====
function demCongViec(trangThai = null) {
if (trangThai === null) return danhSachCongViec.length;
return danhSachCongViec.filter(cv => cv.trangThai === trangThai).length;
}
// ===== FORM HANDLER =====
const form = document.getElementById('form-cong-viec');
form.addEventListener('submit', function(event) {
event.preventDefault();
const ten = document.getElementById('ten-cong-viec').value;
const moTa = document.getElementById('mo-ta').value;
if (themCongViec(ten, moTa)) {
form.reset();
hienThiDanhSach();
}
});✅ Checklist hoàn thành
- [ ] Đã tạo mảng
danhSachCongViec - [ ] Đã tạo hàm
kiemTraTenCongViec() - [ ] Đã tạo hàm
themCongViec()với validation - [ ] Đã tạo hàm
hienThiDanhSach()hiển thị ra console - [ ] Form submit gọi hàm
themCongViec() - [ ] Form reset sau khi thêm thành công
- [ ] Có thể thêm nhiều công việc và xem danh sách
- [ ] Đã tạo hàm đếm công việc
🧪 Checkpoint
Câu hỏi:
- Tại sao dùng
Date.now()để tạo ID? forEachkhác gì vớiforloop?- Hàm
filterlàm gì? - Khi nào dùng
returntrong hàm?
Đáp án:
Date.now()trả về timestamp duy nhất (milliseconds)forEachlà method của array, gọn hơn, không cần indexfiltertạo mảng mới chứa các phần tử thỏa điều kiện- Dùng
returnđể trả về giá trị và kết thúc hàm sớm
📝 Bài tập về nhà
- Thêm validation cho mô tả (tối đa 500 ký tự)
- Thêm hàm
timCongViec(id)để tìm công việc theo ID - Thêm hàm
xoaCongViec(id)để xóa công việc (tạm thời bằng console) - Thêm hàm
capNhatTrangThai(id, trangThai)để cập nhật trạng thái
💡 Tips
- Luôn validate dữ liệu trước khi xử lý
- Dùng
console.table()để hiển thị dữ liệu dạng bảng - Tạo hàm nhỏ, mỗi hàm làm 1 việc
- Dùng JSDoc comments để mô tả hàm
Chúc bạn hoàn thành tốt! 🚀