Bài trước khép lại bằng một quy tắc: notification để báo, dialog để hỏi. Toast hiện ở góc, không chặn, dễ bị bỏ qua — sai chỗ cho một câu hỏi quan trọng như "Bạn chắc chắn xoá?". Khi cần một quyết định trước khi đi tiếp, bạn dùng dialog service: mở một hộp thoại chặn giữa màn hình, và chỉ tiếp tục khi người dùng đã chọn.

dialog.add(Component, props)

Lấy service bằng useService("dialog"), rồi add(Component, props, options). Điểm khác biệt lớn so với notification: bạn truyền vào một component để làm nội dung hộp thoại, chứ không chỉ một chuỗi. Odoo có sẵn ConfirmationDialog — mẫu hộp xác nhận với tiêu đề, nội dung và hai nút:

Ảnh chụp đoạn mã nền tối minh hoạ dùng dialog service, import ConfirmationDialog từ web core confirmation_dialog, lấy service bằng useService dialog, method xacNhanXoa gọi this dialog add với ConfirmationDialog và một object props gồm title Xoá toàn bộ thẻ, body mô tả thao tác xoá tất cả 4 thẻ, confirmLabel Xoá hết, confirmClass btn-danger, cancelLabel Thôi giữ lại, chú thích confirm và cancel là callback chạy khi người dùng bấm, confirm gọi this thucSuXoa và cancel là hàm rỗng, cuối cùng chú thích hộp thoại chặn toàn màn hình tới khi người dùng chọn xong

Hình 1: dialog.add(ConfirmationDialog, props). Các prop quan trọng: title, body (nội dung), confirmLabel/cancelLabel (chữ trên nút), confirmClass (btn-danger cho hành động nguy hiểm), và quan trọng nhất — confirm/cancel là callback chạy khi người dùng bấm nút tương ứng. Logic thật (xoá, lưu...) nằm trong confirm.

Hộp thoại thật, và nó chặn

Gắn nút "Xoá toàn bộ thẻ…" vào bảng điều khiển, bấm vào. Hộp thoại hiện ra giữa màn hình:

Ảnh chụp thật hộp thoại xác nhận trong Odoo 19, nền phía sau bị làm mờ tối cho thấy bị chặn, ở giữa là một hộp trắng tiêu đề Xoá toàn bộ thẻ kèm nút đóng hình chữ X, nội dung Thao tác này sẽ xoá tất cả 4 thẻ thành viên và không thể hoàn tác Bạn chắc chứ, phía dưới có hai nút một nút Xoá hết màu đỏ và một nút Thôi giữ lại màu xám

Hình 2: Hộp thoại thật. Để ý nền phía sau bị làm mờ — đó là dấu hiệu của việc chặn: người dùng không thao tác được gì khác cho tới khi chọn "Xoá hết" hoặc "Thôi, giữ lại" (hay bấm X để đóng). Nút "Xoá hết" mang class btn-danger (đỏ) vì đây là hành động nguy hiểm. Bấm nút nào thì callback tương ứng chạy — không cần bạn tự lắng nghe sự kiện.

Chính sự chặn này là lý do tồn tại của dialog: nó buộc người dùng đối diện quyết định, không để họ lướt qua như một cái toast tự tắt.

Không chỉ xác nhận

ConfirmationDialog là mẫu tiện lợi cho câu hỏi có/không. Nhưng dialog.add nhận bất kỳ component OWL nào làm nội dung — bạn dựng được hộp thoại phức tạp tuỳ ý: một form nhỏ nhiều trường, một danh sách để chọn, một wizard mini. Component đó thường bọc trong Dialog (component nền, import { Dialog } from "@web/core/dialog/dialog") để có sẵn khung, tiêu đề, nút đóng và lớp nền mờ. ConfirmationDialog chính là một component như vậy dựng sẵn cho bạn.

Vài lưu ý:

  • Logic nằm trong callback, không phải sau lời gọi. dialog.add(...) trả về ngay (không "chờ" người dùng theo kiểu blocking đồng bộ); việc thật xảy ra trong confirm/cancel. Đừng viết code "sau khi xoá" ngay dưới dialog.add — nó chạy trước khi người dùng kịp bấm.
  • confirmClass="btn-danger" cho hành động phá huỷ. Màu đỏ là tín hiệu thị giác để người dùng khựng lại trước khi bấm.
  • Dialog chồng được. Mở dialog từ trong một dialog khác vẫn hoạt động; Odoo quản lý chồng lớp.

notification vs dialog — nhắc lại cho chắc

notification (toast) dialog (hộp thoại)
Vị trí góc phải giữa màn hình
Chặn thao tác Không Có
Dùng để báo kết quả hỏi một quyết định
Tự tắt có (trừ sticky) không, chờ người dùng

Cần cho người dùng biết việc vừa xong → toast. Cần một câu trả lời trước khi tiếp → dialog.

Ba ý mang về

  1. dialog.add(Component, props) mở một hộp thoại chặn giữa màn hình; khác notification, nó nhận một component làm nội dung và chặn cho tới khi người dùng chọn xong.
  2. ConfirmationDialog là mẫu có sẵn cho câu hỏi có/không: title, body, confirmLabel/cancelLabel, confirmClass, và callback confirm/cancel chứa logic thật. Dùng btn-danger cho hành động nguy hiểm.
  3. Logic nằm trong callback, không sau lời gọi — dialog.add trả về ngay. Cần hộp thoại phức tạp thì truyền component OWL riêng (thường bọc Dialog) thay cho ConfirmationDialog.

Mọi component JS/XML ta viết đều phải nằm trong một bundle asset để web client nạp. Phần sau làm rõ assets bundle: các bundle chính (web.assets_backend, web.assets_frontend...), thêm file vào đúng bundle, và vì sao đặt nhầm bundle là code không bao giờ chạy.