Đến giờ ta viết Python phía server. Nhưng giao diện Odoo 19 chạy trong trình duyệt bằng một framework riêng: OWL (Odoo Web Library) — kiểu React/Vue nhưng của Odoo. Khi bạn muốn một màn hình tự vẽ thay vì list/form có sẵn — một dashboard, một widget tương tác — bạn viết một component OWL. Bài này giải phẫu component đầu tiên: một bảng điều khiển lấy số liệu thật và hiển thị.

Ba mảnh của một component

Một component OWL trong Odoo luôn gồm ba phần rời nhau, phải khớp tên với nhau:

Ảnh sơ đồ ba hộp trên nền kem tiêu đề Ba mảnh của một component OWL trong Odoo. Hộp một Lớp JS ghi class extends Component, static template bằng tên, setup hook state, file js trong static src js. Dấu cộng. Hộp hai Template QWeb-OWL ghi thẻ t t-name mod chấm Tên, HTML cùng t-esc t-if t-on, đọc state x, file xml trong static src xml. Mũi tên. Hộp ba Đăng ký ghi registry category actions, chấm add tag Comp, cộng ir actions client, cộng khai vào assets bundle. Dòng dưới ghi nạp cả ba phải nằm trong bundle web assets_backend trong manifest, sau khi minus u phải hard refresh trình duyệt để lấy asset mới, và lỗi OWL chỉ hiện ở trình duyệt server im lặng soi bằng Console hoặc pageerror

Hình 1: Ba mảnh. (1) Lớp JS — class extends Component, khai static template và một setup(). (2) Template QWeb-OWL (.xml) — HTML kèm chỉ thị t-esc/t-if/t-on-click, đọc dữ liệu từ state. (3) Đăng ký — đưa component vào registry để Odoo biết tag nào ứng với component nào. Cả ba phải nằm trong bundle web.assets_backend khai ở manifest; đổi xong phải hard refresh trình duyệt.

Lớp JS: setup, state, và ORM

Đây là trái tim của component — không có constructor, mọi thứ khởi tạo trong setup():

Ảnh chụp đoạn mã JavaScript nền tối file dashboard.js, đầu file có chú thích odoo-module, import Component useState onWillStart từ @odoo/owl, import registry từ @web core registry, import useService từ @web core utils hooks. Khai class QuanDashboard extends Component với static template bằng quan_ca_phe.Dashboard chú thích tên template QWeb-OWL, method setup chú thích chạy khi khởi tạo không dùng constructor, gán this orm bằng useService orm chú thích hook lấy service ORM, this state bằng useState với tong diem vang loading, onWillStart async chú thích chạy trước khi render lần đầu gọi this orm searchRead quan.the.thanh.vien lấy diem và hang_the, gán this state tong bằng cards length chú thích gán state UI tự vẽ lại, this state diem bằng reduce cộng dồn diem, this state loading false, cuối file registry category actions add quan_ca_phe.dashboard QuanDashboard

Hình 2: Component QuanDashboard. setup() chạy lúc khởi tạo (thay cho constructor). Ba thứ then chốt: useService("orm") lấy cầu nối tới server để gọi ORM; useState({...}) tạo trạng thái phản ứng — gán lại state.tong là giao diện tự vẽ lại; onWillStart chạy trước lần render đầu, nơi ta await orm.searchRead(...) để nạp số liệu. Dòng cuối registry.category("actions").add(...) đăng ký component làm một client action.

Kết quả: dashboard chạy thật trong trình duyệt

Mở menu Bảng điều khiển, OWL dựng component và điền số liệu lấy từ server:

Ảnh chụp giao diện dashboard OWL thật trong Odoo, tiêu đề Bảng điều khiển Thẻ thành viên kèm biểu tượng ly cà phê, ba thẻ số cạnh nhau: thẻ đầu số 4 màu nâu nhãn Tổng số thẻ, thẻ giữa số 385 màu xanh lá nhãn Tổng điểm tích luỹ, thẻ cuối số 1 màu vàng nhãn Thẻ hạng Vàng

Hình 3: Dashboard do OWL tự vẽ, số liệu thật lấy qua ORM: 4 thẻ, tổng 385 điểm (150+150+85+0), 1 thẻ hạng Vàng. Đây không phải view list/form có sẵn — mỗi pixel do component của ta điều khiển. Đường đi: onWillStart gọi searchRead → gán vào state → template đọc state.tong/state.diem/state.vang và render ba thẻ. Chụp bằng trình duyệt thật, không có lỗi Console nào.

Vài lưu ý sống còn khi làm OWL

  • Lỗi OWL chỉ hiện ở TRÌNH DUYỆT. Server không báo gì — một template sai t- hay một biến undefined chỉ nổ ở Console. Luôn mở DevTools (hoặc bắt pageerror bằng công cụ tự động) khi component không hiện.
  • Nạp asset qua bundle. .js và .xml phải khai trong web.assets_backend của manifest; thiếu là component không tồn tại với trình duyệt.
  • Sau -u phải hard refresh (Ctrl+Shift+R). Trình duyệt cache asset cũ; sửa JS mà không refresh mạnh là bạn đang xem bản cũ và tưởng code không ăn.
  • useState mới phản ứng. Gán vào một thuộc tính thường (this.tong = 5) không làm UI vẽ lại; phải là this.state.tong với state tạo từ useState.

Ba ý mang về

  1. Component OWL gồm ba mảnh: lớp JS (class extends Component, static template, setup), template QWeb-OWL (.xml), và đăng ký vào registry — cả ba khai trong bundle web.assets_backend.
  2. setup() thay constructor: dùng useService("orm") để gọi server, useState({...}) cho trạng thái phản ứng, onWillStart để nạp dữ liệu trước lần render đầu.
  3. Lỗi OWL chỉ ở trình duyệt — mở Console; và sau mỗi lần -u phải hard refresh để lấy asset mới.

Component đã hiện ba con số. Nhưng chính xác t-esc="state.tong" làm gì, và khác t-out chỗ nào? Phần sau đào vào template OWL: t-esc, t-out, t-if — cách đổ dữ liệu vào HTML an toàn và điều khiển hiển thị.