Chào các bạn, trong bài viết trước chúng ta đã lập trình một số chức năng cho ứng dụng todolist như:

  • Lấy danh sách công việc hiện có
  • Thêm công việc
  • Xóa công việc

Trong bài viết này, chúng ta sẽ hoàn thiện ứng dụng với các chức năng còn lại :

  • Lọc công việc theo trạng thái (tất cả, các công việc đã hoàn thành, các công việc chưa hoàn thành)
  • Thay đổi trạng thái của công việc (hoàn thành, chưa hoàn thành)
  • Sửa công việc

Trước khi đọc bài viết này các bạn có thể tham khảo lại các bài viết trước để tìm hiểu và thực hành

Danh sách bài viết:

  1. Ứng dụng todolist + Kết nối API : Khởi tạo server (Phần 1)
  2. Ứng dụng todolist + Kết nối API : Lập trình chức năng (Phần 2)
  3. Ứng dụng todolist + Kết nối API : Deploy ứng dụng lên Heroku (Phần 4)

Bắt đầu tiến hành thôi nào 😁😁😁

Thay đổi trạng thái công việc

Một công việc sẽ có trạng thái hoàn thành ( status = true) hoặc chưa hoàn thành ( status = false). Chúng ta có thể chuyển đổi qua lại trạng thái của các công việc này bằng cách, click vào ô checkbox ở trên giao diện

Nếu checkbox được check => công việc hoàn thành, ngược lại checkbox được uncheck => công việc chưa hoàn thành

Đầu tiên chúng ta sẽ định nghĩa function updateTodoAPI có chức năng gọi API để cập nhật công việc, API này sẽ làm chung chức năng : Cập nhật trạng thái và cập nhật tiêu đề của công việc

// Cập nhật todo
function updateTodoAPI(todo) {
    return axios({
        method: "put",
        url: `/todos/${todo.id}`,
        data: todo,
    });
}

Tiếp đến chúng ta sẽ gán sự kiện vào ô input để khi có sự kiện tác động vào ô input thì hàm xử lý sẽ được gọi. Tương tự như chức năng xóa công việc, chúng ta sẽ gán sự kiện lúc render todo

function renderUI(arr) {
    // ...
    <input
        type="checkbox"
        ${t.status ? "checked" : ""}
        onclick="toggleStatus(${t.id})"
    >
    // ...
}

Tiếp đến chúng ta định nghĩa hàm xử lý toggleStatus

Trong hàm xử lý này, chúng ta tìm ra công việc muốn thay đổi trạng thái dựa trên id, sau đó gọi updateTodoAPI để thực hiện cập nhật trạng thái công việc mới

// Thay đổi trạng thái todo
async function toggleStatus(id) {
    try {
        let todo = todos.find((todo) => todo.id == id);
        todo.status = !todo.status;

        let res = await updateTodoAPI(todo);

        todos.forEach((todo, index) => {
            if (todo.id == id) {
                todos[index] = res.data;
            }
        });
        renderUI(todos);
    } catch (error) {
        console.log(error);
    }
}

Lọc công việc theo trạng thái

Đối với chức năng lọc công việc theo trạng thái, mặc định chúng ta sẽ hiển thị tất cả danh sách công việc, ngoài ra chúng ta có thể lọc các công việc đã hoàn thành hoặc chưa hoàn thành tùy theo mục đích sử dụng

Đầu tiên chúng ta sẽ truy cập vào tất cả các ô input chứa các lựa chọn của trạng thái công việc

Tiếp theo lắng nghe sự kiện trên từng ô input để xem người dùng đạng lựa chọn ô input nào và lấy value tương ứng của ô input đó

  • Tất cả : "all"
  • Hoàn thành : "active"
  • Chưa hoàn thành : "unactive"
const todo_option_item = document.querySelectorAll(".todo-option-item input");

// Lấy giá trị trong 1 ô input radio
function getOptionSelected() {
    for (let i = 0; i < todo_option_item.length; i++) {
        if (todo_option_item[i].checked) {
            return todo_option_item[i].value;
        }
    }
}

todo_option_item.forEach((btn) => {
    btn.addEventListener("change", function () {
        let optionSelected = getOptionSelected();
        renderTodo(optionSelected);
    });
});

Rất may là json-server hỗ trợ cho chúng ta sử dụng query string để phục vụ cho chức năng này

Chúng ta sẽ sửa lại function getTodosAPI cho phù hợp với mục đích

function getTodosAPI(status) {
    switch (status) {
        case "all": {
            return axios.get("/todos");
        }
        case "active": {
            return axios.get("/todos?status=true");
        }
        case "unactive": {
            return axios.get("/todos?status=false");
        }
        default: {
            return axios.get("/todos");
        }
    }
}

Cập nhật tiêu đề công việc

Khi chúng ta muốn sửa tiêu đề của công việc, chúng ta sẽ bấm vào icon " sửa" => tiêu đề của công việc sẽ xuất hiện trong ô input, đồng thời nút " Thêm" lúc này sẽ trở thành nút " Cập nhật"

Để lưu được công việc nào muốn được update, chúng ta định nghĩa biến idUpdate để lưu id của công việc, và biến isUpdate để biết khi nào chúng ta update công việc, khi nào thêm công việc

let isUpdate = false;
let idUpdate = null;

Trong function renderUI, lúc render ra công việc, chúng ta sẽ gán luôn sự kiện onlick với hàm xử lý là updateTitle vào button icon này

function renderUI(arr) {
    // ...

    <button class="btn btn-update" onclick="updateTitle(${t.id})">
            <img src="./img/pencil.svg" alt="icon">
    </button>

    // ...
}

Trong function updateTitle chúng ta sẽ tìm kiếm thông tin của công việc ứng với id được truyền vào, lúc này sẽ lưu id của công việc vào biến idUpdate, và biến isUpdate = true => tôi muốn cập nhật công việc này

Nút " Thêm" bây giờ sẽ chuyển thành nút " Cập nhật"

// Cập nhật tiêu đề todo
function updateTitle(id) {
    let title;
    todos.forEach((todo) => {
        if (todo.id == id) {
            title = todo.title;
        }
    });

    btn_add.innerText = "CẬP NHẬT";

    todo_input.value = title;
    todo_input.focus();

    idUpdate = id;
    isUpdate = true;
}

Trong phần lắng nghe sự kiện của DOM element " btn_add", vì lúc này element này vừa thực hiện chức năng thêm, vừa thực hiện chức năng cập nhật. Chúng ta cần biết giá trị của biến isUpdate để có những xử lý tương ứng

  • Nếu isUpdate = true => Cập nhật công việc
  • Nếu isUpdate = false => Thêm công việc
btn_add.addEventListener("click", function () {
    let todoTitle = todo_input.value;
    if (todoTitle == "") {
        alert("Nội dung không được để trống!");
        return;
    }
    if (isUpdate) {
        let todo = todos.find((todo) => todo.id == idUpdate);
        todo.title = todoTitle;

        updateTodo(todo);
    } else {
        createTodo(todoTitle);
    }

    todo_input.value = "";
});

Phần hàm xử lý công việc, chúng ta sẽ định nghĩa function updateTodo để xử lý, trong function này chúng ta gọi đến function updateTodoAPI để thực hiện gọi API update tiêu đề công việc

Nếu gọi API thành công, lúc này chúng ta sẽ thực hiện hiển thị tiêu đề mới của công việc lên trên giao diện. Đồng thời reset 2 biến isUpdate = falseidUpdate = null về giá trị mặc định ban đầu của chúng

async function updateTodo(todoUpdate) {
    try {
        let res = await updateTodoAPI(todoUpdate);

        todos.forEach((todo, index) => {
            if (todo.id == todoUpdate.id) {
                todos[index] = res.data;
            }
        });

        btn_add.innerText = "Thêm";
        isUpdate = false;
        idUpdate = null;

        renderUI(todos);
    } catch (error) {
        console.log(error);
    }
}

Vậy là ứng dụng todolist của chúng ta đã hoàn thành các chức năng cần thiết, ngoài ra các bạn có thể phát triển thêm các tính năng khác như :

  • Phân trang, nếu danh sách công việc nhiều
  • Tìm kiếm công việc
  • ...

Trong phần tiếp theo chúng ta sẽ deploy ứng dụng mà chúng ta vừa hoàn thiện lên heroku

Hi vọng các bạn thấy bài viết có ích và ủng hộ. Thanks 😍😍

Các bạn có thể tham khảo source code của bài viết này tại đây : https://github.com/buihien0109/todolist-api

Các bạn có thể tham khảo thêm khóa học này nhé:

  • Javascript căn bản - Tổng hợp 12 game huyền thoại - tại đây.
  • Lập trình Game Javascript (trực tuyến có tương tác) - tại đây.