PopupEditor (modal popup)
Dùng PopupEditor khi muốn 1 form mở chồng lên màn hình hiện tại — sửa 1 record, dialog confirm, picker.
Khi nào dùng PopupEditor?
Dùng khi user không rời màn hình hiện tại — chỉ mở 1 form chồng lên để làm 1 việc nhanh, xong đóng quay về.
Ví dụ điển hình:
- Sửa 1 record: list xe hiển thị → double-click row → popup edit chi tiết xe.
- Dialog confirm: hỏi “có chắc muốn xóa?” + textarea lý do.
- Picker phức tạp: chọn khách hàng có filter nhiều trường (search nâng cao).
→ Cần màn hình đứng độc lập mà user có thể mở nhiều cùng lúc → dùng TabEditor thay vì PopupEditor.
Khai báo class
using Core.Components.Forms;
using TMS.API.Models;
namespace TMS.UI.Business.Freight
{
public class CustomerDetailBL : PopupEditor
{
public CustomerDetailBL() : base(nameof(Customer))
{
Title = "Sửa khách hàng";
Icon = "icons/edit.png";
}
}
}
Y hệt cách viết TabEditor, chỉ khác kế thừa PopupEditor. Core sẽ render thành modal có backdrop + draggable header thay vì tab.
Mở popup từ code
// Helper Popup() — mở 1 popup
await this.Popup(nameof(Customer), () => new CustomerDetailBL
{
Entity = customer, // pass entity vào để edit
});
Nếu cần ID ổn định (để mở popup cùng record 2 lần thì focus popup cũ):
await this.OpenTab(
"Customer_" + customer.Id,
"Customer",
() => new CustomerDetailBL { Entity = customer },
popup: true);
Property hay set
Giống TabEditor, plus:
| Property | Mục đích |
|---|---|
Title | Tiêu đề trên header popup. |
Entity | Entity được edit. Quan trọng: PopupEditor mặc định ShouldLoadEntity = false — bạn phải tự set Entity trong factory hoặc bật ShouldLoadEntity = true để Core load. |
Icon | Icon trên header. |
ChildForm | True → khi đóng, không refresh parent (mặc định false sẽ refresh). |
Pattern: list mở popup edit
Đây là pattern thường gặp nhất — list TabEditor mở chi tiết PopupEditor:
public class CustomerListBL : TabEditor
{
public CustomerListBL() : base(nameof(Customer))
{
Title = "Khách hàng";
DOMContentLoaded += () =>
{
var grid = this.FindComponentByName<GridView>("Grid");
if (grid != null)
{
grid.DblClick = row =>
{
var customer = (Customer)row;
this.OpenTab("Customer_" + customer.Id, "CustomerEditor",
() => new CustomerDetailBL { Entity = customer },
popup: true);
};
}
};
}
// Button "Thêm mới" — config row Component có Events="AddNew"
public void AddNew(object arg)
{
this.Popup(nameof(Customer), () => new CustomerDetailBL
{
Entity = new Customer { Active = true }
});
}
}
Đóng popup
User tự đóng qua nút X trên header. Hoặc auto đóng sau khi save:
public class CustomerDetailBL : PopupEditor
{
public CustomerDetailBL() : base(nameof(Customer))
{
Title = "Sửa khách hàng";
AfterSaved = ok => { if (ok) Dispose(); }; // đóng nếu save success
}
}
ConfirmDialog — popup nhỏ chuyên biệt
Cho yes/no đơn giản, không cần viết hẳn 1 class PopupEditor — dùng ConfirmDialog có sẵn:
var confirm = new ConfirmDialog
{
Title = "Xác nhận",
Content = "Bạn có chắc muốn xóa các dòng đã chọn?",
NeedAnswer = false, // true → có thêm textarea nhập lý do
TabEditor = TabEditor, // anchor vào tab/popup hiện tại
};
confirm.YesConfirmed += async () =>
{
// user bấm Yes
await DeleteRows();
Toast.Success("Đã xóa");
};
confirm.Render();
→ Pattern thực từ TMS.UI/Business/Freight/CoordinationContainerBL.cs.
Bẫy hay gặp
- Quên set
Entitytrong factory → form trống. PopupEditor không auto-load (defaultShouldLoadEntity = false). - Pop-up trong pop-up trong pop-up → UX rất khó dùng. Cân nhắc lại design (thường dùng grid inline edit hoặc panel side-by-side thay vì 3 cấp popup).
- Quên
confirm.Render()vớiConfirmDialog→ dialog không hiện. (Sự khác biệt vớiPopup()extension —ConfirmDialogcần gọiRender()thủ công.)
Pattern thực từ TMS.UI
TMS.UI/Business/Freight/DriverContainerDetailBL.cs— popup chi tiết tài xế-container, overrideSavevalidate,AfterSavedrefresh, mở popup con khác (TruckStopHistory).TMS.UI/Business/Freight/DriverTruckDetailBL.cs— popup chi tiết tài xế-xe.