2012-09-01

ZK-1310 的故事

來寫一篇 ZK-1310 的檢討報告 (誰叫我亂發 issue)

起因是這一個 forum thread。 在這裡先 murmur 一下,ZK 截至目前為止是沒有 用 MVVM 寫一個 Tree 的官方範例; MVC 版是有,可是... 只能說那個建立 TreeModel 的 code 實在是太銷魂了, 而且在我修改之前的程式碼根本不能跑。 另外也很好奇,自己從 MVC 版改裝成 MVVM 有那麼難嗎? 為什麼一堆歪果人在 forum 上頭掱呢?果然付錢用 ZK 跟拿錢用 ZK 差很多 [淚目]

啊啊... 是檢討報告、不是 complain 大會... [逃]

故事到底是怎麼發生的呢?當你用 MVVM 操作一個 Tree

<div apply="org.zkoss.bind.BindComposer" 
 viewModel="@id('vm') @init('org.zkoss.rtmf.vm.TreeVM')">
    <tree checkmark="true" model="@load(vm.treeModel)">
        <template name="model" var="foo"><treeitem>
            <treerow>
                <treecell label="@load(foo.data.id)"></treecell>
            </treerow>
        </treeitem></template>
    </tree>
</div>  

TreeVM 長這樣:

public class TreeVM{
    private DefaultTreeNode<Product> selectedProduct;

    public DefaultTreeNode<Product> getSelectedProduct() {
        return selectedProduct;
    }

    @NotifyChange("selectedProduct")
    public void setSelectedProduct(DefaultTreeNode<Product> selectedProduct) {
        this.selectedProduct = selectedProduct;
    }

    public DefaultTreeModel<Product> getTreeModel(){
        DefaultTreeModel<Product> result = MockData.genTreeModel();
        int[] path = {0};
        result.addSelectionPath(path);      
        return result;
    }
}

getTreeModel() 另外用 addSelectionPath() 設定哪一個 item 是 selected 的。 實際運作起來完全正常、第一個 item 會變成 selectedItem。

好的,如果打算讓 VM 去控制 selectedItem, 那麼很直覺地就掛上 selectedItem 這個 attribute,像這樣:

<tree checkmark="true" model="@load(vm.treeModel)" selectedItem="@bind(vm.selectedProduct)">

然後小美家就爆炸了第一個 item 不會變成 selectedItem 了。

為甚麼? <囧>

原因說起來很簡單也很複雜,為了賺不知道在哪裡的稿費,我用複雜一點的方式講 XD。

再更細節我也不太清楚,總之可以確定的是 model 跟其他 attribute 不太一樣, 它會像有特權一樣、無視於 attribute 的順序、甚至應該是脫離傳統程序來處理。 所以,算是順帶一提,如果像下面這個沒有用 model 決定內容的 <listbox>,掛上 selectedIndex

<listbox id="lbx">
    <listitem><listcell label="1" /></listitem>
    <listitem><listcell label="2" /></listitem>
    <listitem><listcell label="3" /></listitem>
</listbox>
<button onClick='lbx.setSelectedIndex(2)' label="selectedIndex=2"/>

就會炸 error:

Out of bound: 2 while size=0

這是因為在 ZK 處理到 <listbox> 的其他 attribute 時還不知道有幾個 children(<listitem>); 如果拿掉 selectedIndex="1",然後選完 item 之後按 show selectedIndex 這個 button,卻沒有問題, 因為這個時候 <listbox> 已經知道。而如果是透過 model 來設定就沒有上述問題,因為它耍特權...... Orz。

交待完黑頁,現在來回歸劇情主軸。所以那個好像出問題的程式,其實運作的順序大概是這樣的:

  1. ZK parse 到 <tree>
  2. 發現有 model,優先處理
    1. TreeVM.getTreeModel() 取得資料
    2. 根據資料設定 <tree> 的內容
  3. 處理 checkmark 這個 attribute
  4. 處理 selectedItem 這個 attribute
    1. TreeVM.getSelectedProduct() 取得資料(是 null)
    2. <tree> 設定 setSelectedItem(null)

於是在 TreeVM.getTreeMode() 當中千辛萬苦設定的 addSelectionPath() 就被蓋掉了 [淚目]。 這樣看起來完全合情合理,壓根不是什麼 bug, 因為只要 TreeVM 在第一次呼叫到 getSelectedProduct() 之前有把 selectedProduct 設定正確就好。 在這個 case 當中,最方便的作法當然就是 getTreeModel()result.addSelectionPath(path); 後頭加上

selectedProduct = result.getChild(path);

就天下太平了。太平歸太平,但是上面這一串,說實在還是有點簡略,因為省略過 view、model、view model 之間互相影響的細節。 單就 ZK-1310 來說,應該已經足夠了。

所謂「江湖一點訣,說出不值錢」,如果當初文件上有記上這一筆, 也就不至於讓一個 developer 去發 forum、讓一個笨蛋 ZK Engineer 跑去發 issue、 最後讓另一個 ZK Engineer 花上半個多小時去思考這個問題。

阿們...... [合掌]

No comments:

Post a Comment