TabEditor (tab full màn hình)
Dùng TabEditor khi muốn màn hình mở dạng tab trong portal. Người dùng có thể mở nhiều tab cùng lúc và chuyển qua lại.
Khi nào dùng TabEditor?
Dùng khi màn hình đứng độc lập — user mở từ menu, làm việc trong tab đó, có thể mở thêm tab khác mà không đóng tab này.
Ví dụ điển hình: list view các record, dashboard, báo cáo.
→ Cần 1 form modal mở chồng lên (sửa 1 record, dialog confirm) → dùng PopupEditor thay vì TabEditor.
Khai báo class
using Core.Components.Forms;
using TMS.API.Models;
namespace TMS.UI.Business.Freight
{
public class CustomerListBL : TabEditor
{
public CustomerListBL() : base(nameof(Customer))
{
Title = "Khách hàng";
Icon = "icons/customers.png";
}
}
}
base(nameof(Customer)) ⇒ Core sẽ:
- Lookup row
FeaturecóName = "Customer"trong DB. - Load tất cả
ComponentGroup(section) thuộc feature đó. - Load tất cả
Component(field) trong mỗi section. - Render layout vào tab.
→ Cấu hình section/component xem Cấu hình UI từ DB.
Mở tab từ code
Tab thường mở từ menu (đã wire sẵn — bạn chỉ thêm row Menu trỏ vào tên class). Nếu cần mở từ code:
// Trong 1 màn hình khác (ví dụ click 1 button)
this.OpenTab("CustomerList", "Customer", () => new CustomerListBL());
| Tham số | Ý nghĩa |
|---|---|
| Id (tham số 1) | ID duy nhất của tab. Gọi 2 lần với cùng ID sẽ focus tab có sẵn. |
| Feature name (2) | Tên feature trong DB. |
| Factory (3) | Hàm khởi tạo class TabEditor của bạn. |
Tip: id ổn định = tab reuse (vd
"Customer_" + customerId). Id ngẫu nhiên = tab mới mỗi lần (vdGuid.NewGuid().ToString()).
Các property property bạn hay set
| Property | Mục đích |
|---|---|
Title | Tiêu đề tab. |
Icon | Đường dẫn icon (path tương đối đến static folder). |
Entity | Entity được edit (set trong factory khi mở tab). |
ShouldLoadEntity | True → Core auto GET /api/<Entity>/{id} khi mount. Default = true. |
ShouldUpdateParentForm | True → khi đóng tab này, refresh tab cha. |
Hook lifecycle
public class CustomerListBL : TabEditor
{
public CustomerListBL() : base(nameof(Customer))
{
Title = "Khách hàng";
// Sau khi DOM render xong (component đã ở trong tree)
DOMContentLoaded += AfterRender;
// Sau khi save
AfterSaved = ok => { if (ok) Toast.Success("Đã lưu"); };
}
private void AfterRender()
{
var grid = this.FindComponentByName<GridView>("Grid");
if (grid != null) grid.DblClick = OnRowDblClick;
}
// Validate trước save: check rule cấu hình DB + business check riêng
public override async Task<bool> Save(object entity)
{
if (!await this.IsFormValid()) return false; // tự check rule + show message
return await base.Save(entity);
}
private void OnRowDblClick(object row)
{
var customer = (Customer)row;
// Mở popup edit (xem PopupEditor)
this.OpenPopup(nameof(Customer), () => new CustomerDetailBL { Entity = customer });
}
}
Method bạn viết được wire vào button thế nào?
Trong màn hình Quản lý Component, set Component.Events = "ExportExcel" cho row của button. Core sẽ tìm method tên ExportExcel trong class TabEditor của bạn và gọi khi click:
public class CustomerListBL : TabEditor
{
public CustomerListBL() : base(nameof(Customer)) { ... }
public void ExportExcel(object arg)
{
var grid = this.FindComponentByName<GridView>("Grid");
// ... code export ...
Toast.Success("Đã xuất Excel");
}
public async Task DeleteSelected(object arg)
{
var grid = this.FindComponentByName<GridView>("Grid");
var selected = grid.GetSelectedRows();
if (selected.Nothing())
{
Toast.Warning("Chọn dòng cần xóa");
return;
}
// ... delete logic ...
}
}
Method có thể void hoặc async Task. Tham số luôn là object arg (Core truyền đối tượng phù hợp tùy ngữ cảnh).
Đóng tab
Hầu hết user tự đóng (click X trên tab strip). Đóng từ code:
this.Dispose(); // gọi từ trong class TabEditor
Pattern thực từ TMS.UI
Xem ví dụ thực:
TMS.UI/Business/Freight/CoordinationContainerBL.cs— list điều phối container, có context menu chuột phải.TMS.UI/Business/Freight/CoordinationTruckBL.cs— list điều phối xe.
Bẫy hay gặp
- Tab cùng id mở 2 lần ⇒ Core focus tab cũ, không tạo mới. Nếu muốn tạo mới mỗi lần → dùng id khác (vd thêm
GetHashCode().ToString()). - Quên
Title→ tab strip hiển thị string rỗng. base(nameof(...))sai tên feature → Core không load được config → tab trắng.- Method
Eventskhông match exact (case-sensitive) → button click không làm gì.