Bridging the Divide: Integrating iDempiere MVC Components into ZK MVVM using Injection
iDempiere

Bridging the Divide: Integrating iDempiere MVC Components into ZK MVVM using Injection

2025-11-12 最後更新:2026-02-20) · 26 分鐘 · Ray Lee (System Analyst)

The iDempiere ERP framework, built upon the foundation of ADempiere, leverages ZK as its core web UI technology. Over the years, ZK has introduced the highly effective Model-View-ViewModel (MVVM) pattern. While MVVM excels at separating UI logic from data state, iDempiere’s established custom components (like WSearchEditor and standard Forms) are deeply rooted in the traditional MVC (Model-View-Controller) pattern.

This blog post explores how to leverage both patterns in a single iDempiere application, specifically focusing on the advanced technique of Component Injection to embed MVC-style components within a modern MVVM-driven View.


1. The Architectural Divide: MVC vs. MVVM in iDempiere

🔸 MVC (Model-View-Controller)

  • Role in iDempiere: Dominates core functionality like standard windows (WEditor, ADForm), where the Controller (Composer or ADForm implementation) directly manipulates the View’s components and handles data synchronization.
  • Key Feature: Components like WSearchEditor are designed to be created and managed programmatically in Java, and they use ValueChangeListener for data binding and synchronization.

🔸 MVVM (Model-View-ViewModel)

  • Role in ZK: Used for modern, highly dynamic screens (like Dashboards and custom forms), where the binding is declared directly in the ZUL file using the @bind syntax.
  • Key Feature: The ViewModel manages the View’s state. Developers avoid direct component manipulation, letting the ZK Binder automatically handle data synchronization between the View and the ViewModel.

The Conflict

When building a custom MVVM Form (e.g., a “Dispensing” screen), developers often need to use powerful iDempiere components like WSearchEditor for lookup fields. Since WSearchEditor is a Java object and not a simple ZK component, the standard ZK @bind annotation fails to work with it.


2. The Solution: Dynamic Component Injection

To bridge this gap, we employ a hybrid approach: using an MVC Controller (Composer) to inject MVC components into an MVVM View.

This technique separates the concerns perfectly:

  1. MVVM (ViewModel): Manages the data state (MFormulaLine objects) and handles business logic (@Command for saving).
  2. MVC (Composer): Handles the UI wiring specific to the custom iDempiere components (WSearchEditor).

Technical Implementation Steps

Let’s use the example of dynamically injecting a WSearchEditor into a Listbox within an MVVM-driven Form.

Step A: Prepare the MVVM View (ZUL)

The ZUL file acts as the View. We replace the standard editable field with an empty container (Listcell) that includes a dynamic index ID.

<listbox id="formulaLineListbox" model="@load(vm.formulaForEdit.components)">
    <template name="model" var="each" status="eachStatus"> 
        <listitem>
            <listcell id="materialTypeCell_@{eachStatus.index}">
                </listcell>
            <listcell>
                <textbox value="@bind(each.name)" /> 
            </listcell>
        </listitem>
    </template>
</listbox>

<window apply="tw.topgiga.rnd.form.FormulaEditComposer" ...>

Step B: The Injection Controller (FormulaEditComposer.java)

This dedicated ZK Composer is responsible for the manual injection and data synchronization.

// FormulaEditComposer.java
public class FormulaEditComposer extends GenericForwardComposer<Window> {

    @Override
    public void doAfterCompose(Window comp) throws Exception {
        super.doAfterCompose(comp);
        
        // Retrieve the data model from the parent ViewModel (often passed as an argument)
        List<MFormulaLine> lines = ... // Get the list of lines

        // Retrieve the Listbox component by its ID
        Listbox listbox = (Listbox) comp.getFellow("formulaLineListbox"); 
        
        // Loop through the data and inject the WSearchEditor
        for (int i = 0; i < lines.size(); i++) {
            MFormulaLine line = lines.get(i);
            
            // 1. Find the target container using the dynamic ID
            Listcell cell = (Listcell) listbox.getFellow("materialTypeCell_" + i); 
            
            // 2. Create the MVC Component (WSearchEditor)
            MLookup lookup = MLookupFactory.get(...); 
            WSearchEditor editor = new WSearchEditor("RND_MaterialType_ID", ..., lookup);

            // 3. Set the initial value from the Model
            editor.setValue(line.getRND_MaterialType_ID()); 
            
            // 4. ***The Crucial Step: Manual Data Write-back***
            editor.addValueChangeListener(evt -> {
                // The MVC Component's event writes the new value directly back 
                // to the MVVM Model object (line).
                line.setRND_MaterialType_ID((Integer) evt.getNewValue());
            });

            // 5. Inject the component's ZK UI part into the Listcell
            cell.appendChild(editor.getComponent()); 
        }
    }
}

3. The Grand Finale: Unified Saving

Once the ValueChangeListener (Step 4 above) is in place, the core Model object (MFormulaLine) is immediately updated in memory whenever the user changes the value in the WSearchEditor.

The final save operation remains purely MVVM:

// MainDispensingVM.java (The MVVM Layer)
@Command
@NotifyChange("projectFormulas")
public void saveEditedFormula() {
    // Because the Composer ensured the Model object (this.formulaForEdit) 
    // is up-to-date, the save command is simple and clean.
    if (this.formulaForEdit.save()) {
        Clients.showNotification("✅ Formula saved successfully!");
    } else {
        Clients.showNotification("❌ Save failed. Check data.");
    }
}

By leveraging a small, dedicated MVC Composer for component injection, we successfully integrate iDempiere’s powerful MVC tooling into a modern ZK MVVM architecture, resulting in highly maintainable and clean code that separates UI wiring from business logic.

日本語版

iDempiere ERPフレームワークは、ADempiereを基盤として構築され、コアWebUIテクノロジーとしてZKを活用しています。長年にわたり、ZKは非常に効果的なModel-View-ViewModel(MVVM)パターンを導入してきました。MVVMはUIロジックとデータ状態の分離に優れていますが、iDempiereの確立されたカスタムコンポーネント(WSearchEditorや標準フォームなど)は、従来のMVC(Model-View-Controller)パターンに深く根ざしています。

このブログ記事では、単一のiDempiereアプリケーションで両方のパターンを活用する方法を探ります。特に、MVCスタイルのコンポーネントをモダンなMVVM駆動のビューに埋め込むための高度なテクニックであるコンポーネントインジェクションに焦点を当てます。


1. アーキテクチャの分断:iDempiereにおけるMVC vs. MVVM

🔸 MVC(Model-View-Controller)

  • iDempiereでの役割: 標準ウィンドウ(WEditorADForm)などのコア機能を支配しており、コントローラー(ComposerまたはADForm実装)がビューのコンポーネントを直接操作し、データ同期を処理します。
  • 主要な特徴: WSearchEditorなどのコンポーネントは、Javaでプログラム的に作成・管理されるように設計されており、データバインディングと同期にValueChangeListenerを使用します。

🔸 MVVM(Model-View-ViewModel)

  • ZKでの役割: ダッシュボードやカスタムフォームなどのモダンで高度に動的な画面に使用され、@bind構文を使用してZULファイルで直接バインディングが宣言されます。
  • 主要な特徴: ViewModelがビューの状態を管理します。開発者は直接的なコンポーネント操作を避け、ZKのBinderがビューとViewModel間のデータ同期を自動的に処理します。

競合の問題

カスタムMVVMフォーム(例:「調剤」画面)を構築する際、開発者はルックアップフィールドにWSearchEditorのような強力なiDempiereコンポーネントを使用する必要がしばしばあります。WSearchEditorはJavaオブジェクトであり、単純なZKコンポーネントではないため、標準的なZKの@bindアノテーションでは動作しません


2. 解決策:動的コンポーネントインジェクション

このギャップを埋めるために、ハイブリッドアプローチを採用します:MVCコントローラー(Composer)を使用して、MVCコンポーネントをMVVMビューにインジェクトします。

このテクニックは関心事を完璧に分離します:

  1. MVVM(ViewModel): データ状態(MFormulaLineオブジェクト)を管理し、ビジネスロジック(保存用の@Command)を処理します。
  2. MVC(Composer): カスタムiDempiereコンポーネント(WSearchEditor)に固有のUI配線を処理します。

技術的な実装手順

MVVM駆動のフォーム内のListboxWSearchEditorを動的にインジェクトする例を使用して説明します。

ステップA:MVVMビュー(ZUL)の準備

ZULファイルがビューとして機能します。標準の編集可能フィールドを、動的インデックスIDを含む空のコンテナ(Listcell)に置き換えます。

<listbox id="formulaLineListbox" model="@load(vm.formulaForEdit.components)">
    <template name="model" var="each" status="eachStatus"> 
        <listitem>
            <listcell id="materialTypeCell_@{eachStatus.index}">
                </listcell>
            <listcell>
                <textbox value="@bind(each.name)" /> 
            </listcell>
        </listitem>
    </template>
</listbox>

<window apply="tw.topgiga.rnd.form.FormulaEditComposer" ...>

ステップB:インジェクションコントローラー(FormulaEditComposer.java)

この専用ZK Composerが、手動インジェクションとデータ同期を担当します。

// FormulaEditComposer.java
public class FormulaEditComposer extends GenericForwardComposer<Window> {

    @Override
    public void doAfterCompose(Window comp) throws Exception {
        super.doAfterCompose(comp);
        
        // 親ViewModelからデータモデルを取得(通常は引数として渡される)
        List<MFormulaLine> lines = ... // 行のリストを取得

        // IDでListboxコンポーネントを取得
        Listbox listbox = (Listbox) comp.getFellow("formulaLineListbox"); 
        
        // データをループしてWSearchEditorをインジェクト
        for (int i = 0; i < lines.size(); i++) {
            MFormulaLine line = lines.get(i);
            
            // 1. 動的IDを使用してターゲットコンテナを検索
            Listcell cell = (Listcell) listbox.getFellow("materialTypeCell_" + i); 
            
            // 2. MVCコンポーネント(WSearchEditor)を作成
            MLookup lookup = MLookupFactory.get(...); 
            WSearchEditor editor = new WSearchEditor("RND_MaterialType_ID", ..., lookup);

            // 3. モデルから初期値を設定
            editor.setValue(line.getRND_MaterialType_ID()); 
            
            // 4. ***重要なステップ:手動データ書き戻し***
            editor.addValueChangeListener(evt -> {
                // MVCコンポーネントのイベントが新しい値を直接
                // MVVMモデルオブジェクト(line)に書き戻す
                line.setRND_MaterialType_ID((Integer) evt.getNewValue());
            });

            // 5. コンポーネントのZK UI部分をListcellにインジェクト
            cell.appendChild(editor.getComponent()); 
        }
    }
}

3. 最終段階:統合された保存処理

上記のステップ4でValueChangeListenerが設定されると、ユーザーがWSearchEditorの値を変更するたびに、コアモデルオブジェクト(MFormulaLine)がメモリ内で即座に更新されます。

最終的な保存操作は純粋なMVVMのままです:

// MainDispensingVM.java(MVVMレイヤー)
@Command
@NotifyChange("projectFormulas")
public void saveEditedFormula() {
    // Composerがモデルオブジェクト(this.formulaForEdit)を
    // 最新の状態に保つことを保証したため、保存コマンドはシンプルでクリーンです。
    if (this.formulaForEdit.save()) {
        Clients.showNotification("✅ 数式が正常に保存されました!");
    } else {
        Clients.showNotification("❌ 保存に失敗しました。データを確認してください。");
    }
}

コンポーネントインジェクション用の小さな専用MVC Composerを活用することで、iDempiereの強力なMVCツールをモダンなZK MVVMアーキテクチャに統合し、UI配線をビジネスロジックから分離した、保守性が高くクリーンなコードを実現できます。

Ray Lee (System Analyst)
作者 Ray Lee (System Analyst)

iDempeire ERP Contributor, 經濟部中小企業處財務管理顧問 李寶瑞