Suốt mấy bài OWL vừa rồi, dòng cuối của mọi file JS đều là một biến thể của registry.category("...").add(...). Client action đăng ký vào "actions", service đăng ký vào "services". Đã đến lúc mổ chính cái cơ chế đó: registry — trái tim của khả năng mở rộng web client Odoo, thứ cho phép module của bạn thêm một mảnh giao diện mà không đụng một dòng nào của lõi.
registry là gì
Web client Odoo không hard-code danh sách các thành phần nó biết. Thay vào đó nó tra một registry — hình dung như một cái tủ nhiều ngăn, mỗi ngăn (category) chứa các mục cùng loại. Muốn thêm một client action? Bỏ nó vào ngăn "actions". Thêm một mục cho thanh trên cùng? Bỏ vào ngăn "systray". Khi web client dựng giao diện, nó duyệt các ngăn này và lấy ra mọi thứ đã đăng ký — kể cả thứ module của bạn vừa bỏ vào.

Hình 1: Vài category hay gặp. Mỗi category là một sổ đăng ký riêng; registry.category("tên") lấy ra sổ đó, .add(key, value) ghi một mục vào. Web client duyệt từng sổ để biết có những client action nào, systray nào, field widget nào... — đó là lý do module bên thứ ba mở rộng được giao diện.
Cấu trúc lời gọi luôn là:
registry.category(TEN_CATEGORY).add(KEY, VALUE, { sequence, force });
KEYlà tên duy nhất trong category đó. Trùng key với một mục đã có sẽ báo lỗi, trừ khi bạn truyền{ force: true }để cố ý ghi đè.VALUEcó hình dạng tuỳ category:"actions"nhận thẳng class Component;"systray"nhận{ Component };"fields"nhận{ component, supportedTypes }.{ sequence }quyết định thứ tự khi category được duyệt.
Đăng ký thật một mục systray
Để thấy registry hoạt động, mình viết một component OWL nhỏ hiện tổng điểm thẻ, rồi đăng ký nó vào category "systray" — ngăn dành cho các mục ở thanh trên cùng bên phải:

Hình 2: Component (trên) và template (dưới). Điểm mấu chốt là ba dòng registry.category("systray").add(...): key duy nhất quan_ca_phe.diem_systray, value là { Component: DiemSystray } (đúng hình dạng category systray đòi), và sequence: 1. Không có dòng này, component tồn tại nhưng web client chẳng bao giờ biết tới nó.
Khai file JS/XML vào bundle web.assets_backend trong manifest, nâng cấp module — và mục xuất hiện ngay trên thanh điều hướng, không sửa gì của Odoo:

Hình 3: Mục systray "☕ 385 điểm" (giữa biểu tượng hoạt động và tên công ty) là component vừa đăng ký. Con số 385 do chính nó gọi orm.searchRead nạp từ model thẻ — tổng điểm thật của 4 thẻ. Nó ngồi cạnh các mục systray sẵn có của Odoo (chat, hoạt động) vì tất cả cùng nằm trong một category.
Vì sao thiết kế kiểu registry
Cách này giải quyết một bài toán nền tảng của phần mềm mở rộng được: làm sao để bên thứ ba thêm hành vi mà không phải sửa (rồi bảo trì bản vá của) mã gốc. Nếu web client giữ một danh sách cứng các systray, thêm một mục mới đồng nghĩa với sửa file lõi Odoo. Với registry, lõi chỉ nói "tôi sẽ hiển thị mọi thứ trong category systray", còn module tự bỏ mục của mình vào. Lõi và module rời nhau hoàn toàn.
Vài điểm thực dụng:
- Key phải có prefix module (
quan_ca_phe.diem_systray) để không đụng key của module khác trong cùng category. { force: true }là con dao hai lưỡi: nó cho phép thay một mục lõi (ví dụ đổi một field widget mặc định), nhưng hai module cùng force một key thì cái nạp sau thắng — khó lần.- Đăng ký chạy khi file JS được nạp, tức khi bundle
web.assets_backendtải. Quên khai file vào bundle trong manifest là component không bao giờ được đăng ký, mà chẳng có lỗi nào.
Ba ý mang về
- registry là hệ thống sổ đăng ký của web client: nhiều category (
actions,systray,fields,views,services,formatters...), mỗi cái chứa các mục cùng loại. Web client duyệt chúng để dựng giao diện — nhờ vậy module mở rộng được mà không sửa lõi. - Mẫu lời gọi luôn là
registry.category(TÊN).add(KEY, VALUE, {sequence, force})—KEYduy nhất (nên có prefix module),VALUEcó hình dạng tuỳ category,sequencequyết thứ tự,forceđể cố ý ghi đè. - Đăng ký chỉ chạy khi file JS được nạp qua bundle
web.assets_backend. Quên khai file vào manifest thì không có đăng ký nào và cũng không có lỗi nào.
Ta đã lướt qua category "fields" mà chưa làm gì với nó. Phần sau bắt tay viết một field widget tuỳ biến bằng OWL — thay cách một field hiển thị và cho nhập trong form, đăng ký đúng vào category đó.