Callout 與 Model Validator:事件驅動開發實戰
iDempiere 提供兩種事件驅動機制:Callout 處理 UI 層即時互動,Model Validator 處理資料層驗證與後處理。本文將詳解兩者差異,並提供完整的實作範例。
一、什麼是 Callout?
Callout 是 UI 層級的回呼機制,當使用者在畫面上修改欄位時立即觸發。
適用場景
- 欄位聯動(選客戶 → 自動帶入地址)
- 即時計算(數量 × 單價 = 金額)
- 動態驗證(檢查庫存是否足夠)
Callout 範例:自動計算金額
public class CalloutBookLoan extends CalloutEngine {
public String calculateFee(Properties ctx, int WindowNo,
GridTab mTab, GridField mField, Object value) {
if (value == null) return "";
// 取得借閱天數
int days = ((BigDecimal) value).intValue();
// 計算費用:每天 10 元
BigDecimal fee = new BigDecimal(days * 10);
// 設定到費用欄位
mTab.setValue("Fee", fee);
return ""; // 空字串表示成功
}
}
註冊 Callout
在 Application Dictionary → Column 找到目標欄位,設定 Callout 欄位:
com.yourcompany.callout.CalloutBookLoan.calculateFee
二、什麼是 Model Validator?
Model Validator 是資料層的攔截器,在資料儲存前後觸發,可用於驗證或後處理。
事件類型
| 事件 | 時機 | 用途 |
|---|---|---|
| BEFORE_NEW | 新增前 | 自動填入預設值 |
| BEFORE_CHANGE | 修改前 | 驗證資料 |
| BEFORE_DELETE | 刪除前 | 檢查是否可刪除 |
| AFTER_NEW | 新增後 | 建立關聯資料 |
| AFTER_CHANGE | 修改後 | 同步更新其他表 |
| AFTER_DELETE | 刪除後 | 清理關聯資料 |
Model Validator 範例
public class BookLoanValidator implements ModelValidator {
private int m_AD_Client_ID = -1;
@Override
public void initialize(ModelValidationEngine engine, MClient client) {
if (client != null)
m_AD_Client_ID = client.getAD_Client_ID();
// 註冊要監聽的表
engine.addModelChange(MBookLoan.Table_Name, this);
}
@Override
public String modelChange(PO po, int type) throws Exception {
if (po instanceof MBookLoan) {
MBookLoan loan = (MBookLoan) po;
if (type == TYPE_BEFORE_NEW || type == TYPE_BEFORE_CHANGE) {
// 驗證:借出時必須有借閱人
if (loan.isBorrow() && loan.getAD_User_ID() <= 0) {
return "借出時必須指定借閱人";
}
}
if (type == TYPE_AFTER_CHANGE) {
// 記錄借閱歷史
if (loan.is_ValueChanged("IsBorrow")) {
createHistory(loan);
}
}
}
return null; // null 表示通過
}
@Override
public String docValidate(PO po, int timing) {
return null; // 不處理文件事件
}
@Override
public int getAD_Client_ID() {
return m_AD_Client_ID;
}
}
註冊 Model Validator
在 Application Dictionary → Model Validator 新增記錄:
- Name: BookLoanValidator
- Entity Type: 選擇你的 Entity
- Model Validation Class:
com.yourcompany.validator.BookLoanValidator
三、Callout vs Model Validator 比較
| 特性 | Callout | Model Validator |
|---|---|---|
| 層級 | UI | Model (資料) |
| 觸發 | 欄位變更時 | 儲存時 |
| API 呼叫 | ❌ 不觸發 | ✅ 會觸發 |
| Import | ❌ 不觸發 | ✅ 會觸發 |
| 可取消操作 | ✅ 可以 | ✅ 可以 |
| 效能影響 | 每次輸入都執行 | 只在儲存時執行 |
四、最佳實踐
- UI 即時回饋 → 用 Callout
- 資料驗證 → 用 Model Validator(確保 API 也會驗證)
- 商業邏輯 → 放在 Model Validator 或 Model 類別
- 避免在 Callout 存取資料庫,會影響 UI 效能
常見問題 FAQ
Q1: Callout 回傳值代表什麼?
回傳空字串 "" 表示成功,回傳錯誤訊息字串會顯示在 UI 上。
Q2: Model Validator 可以擋下儲存嗎?
可以!在 modelChange() 回傳錯誤訊息字串,系統會 rollback 並顯示錯誤。
Q3: 用 API 新增資料會觸發 Callout 嗎?
不會。Callout 只在 UI 操作時觸發。如果要確保驗證,必須用 Model Validator。
Q4: 如何偵錯 Callout/Validator?
使用 log.info() 或 log.warning() 輸出到 log 檔案,或用 IDE 設中斷點 debug。
Q5: 一個欄位可以有多個 Callout 嗎?
可以,用分號 ; 分隔多個 Callout class,會依序執行。
發佈留言