ZK

還有一些詰譙成份居多的散記在 blameZK 上。

ZUL Coding Style

  • 使用 <?import wtf.foo> 而不是在 <zscript> 中寫 import wtf.foo;

Eclipse

測試環境:

  • Eclipse Luna

  • Maven project

用 Server View 指定外部 Tomcat、然後 project 用 Run on Server 的方法。

用內建 XML Editor 修改 ZUL

  1. 開 Preferences

  2. 左邊選擇 General / Content Types

    1. 右邊中間選擇 Text / XML (Illformed)

    2. 新增 *.zul

  3. 左邊選擇 General / File Associations

    1. 新增 *.zul

    2. 下方 Associated editors 選擇 XML Editor 後按下「Default」按鈕

其他哏

  • 用 external editor 修改 ZUL,

    需要要求 Tomcat 重新 publish 才能讀得到,不然就是 Navigator View 重新 refresh。

    簡單地說就是 Eclipse 不知道 ZUL 改了,所以不會叫 Tomcat 更新 ZUL。

    • 另外要注意 Navigator View 當下必須包含該 ZUL(如果有作 zoom in 就會有這個問題),

      不然重新 publish 還是一樣讀到舊的...

    • 有遇到 publish 也無法更新 ZUL 的狀況,一定要到 Navigator View 作 refresh 才行。

      沒特殊需求還是乖乖用 Eclipse 內建 XML editor 就算了... [死]

  • 修改 MVVM 的 ViewModel,不用想了,一定要重新 restart Tomcat

MVVM

wire 相關

使用 @Wire@WireVariable 必須在 @Init 的 method 以後才會有實際值。

注意Window 是獨立的 IdSpace,也就是你無法直接 wire Window 裡頭的 component, 而必須從 Window 開始指進去,例如 @Wire("#fooWindow #foo")

觸發 NotifyChange 的其他方法

透過 MVVM 觸發了一個 @Command method doFoo(), 在 doFoo() 中呼叫另一個 @Command method doWTF(), 這裡必須注意在 doFoo() 中呼叫 doWTF() 是單純的 Java method call, 跟 MVVM 一點關係也沒有,所以 doWTF() 掛的那堆 @NotifyChange 完全不會被觸發。

解決方法是自己發 event 給 MVVM 的機制,例如:

String[] propertyList = {};
for (String property : propertyList) {
    BindUtils.postNotifyChange(null, null, this, property);
}

另一種解法是 doFoo() 必須長這樣

@Command
public void doFoo(@ContextParam(ContextType.BINDER) final Binder binder) {
    //......

    //doWTF()
    binder.postCommand("doWTF", null);
}

這樣是透過 MVVM 底層機制去觸發 doWTF(),相關的 @NotifyChange 自然也會觸發了。

Form Binding

假設 form 長這樣:

form="@id('fx') @load(vm.currentData) @save(vm.currentData, before='save')"

如果有對 currentData 作 notify change,那有 load fx 或是 fxStatus 也會隨之 notify change。

假設有一個 field 的 binding 對象是用 ListModelList,像這樣:

在 VM 當中作 fooList.setSelection(Arrays.asList(aFoo)) 只會讓畫面顯示正常, 並不會讓 fx.foo 的值變成 aFoo, 必須自己作 form.setField("foo", aFoo)

動態改變 middle object 的 class ###

如果 middle object 的 class 會在操作當中改變 (你問為什麼會有這種需求?這是一個很長的故事... [淚目]), 這時會炸出一個 exception,炸點在 ZUL 宣告 form binding 的附近, 內容主要是 property binding 錯誤。 目前找到唯一的解法,就是自己實作一個 MyForm,內容完全照抄 org.zkoss.bind.impl.FormImpl,然後加上:

public void clear() {
    _saveFieldNames.clear();
    _loadFieldNames.clear();
    _fields.clear();
    _initFields.clear();
    _dirtyFieldNames.clear();
}

這樣在切換時先呼叫 clear() 再 notify change binding 的 instance, 就不會炸了 \囧/

雜項

如果 bind ParentVM 的 component 裡頭有一個 child component 是 bind ChildVM, 而 ParentVMChildVM 都有掛 @Command 的 method 假設叫做 wtf()。 那麼,在 child component 裡頭的 @command('wtf') 只會觸發 ChildVM.wtf(),不會連帶觸發 ParentVM.wtf()

@load() 可以用來呼叫 VM 的 method, 例如 @load(vm.foo(each)) 就是呼叫 foo(),然後傳入 each

這樣子作可以取得發出 event 的 component

@Command
public void wtf(@ContextParam(ContextType.COMPONENT) Component component) { }

這樣子作可以取得發出 event 的 event

@Command
public void wtf(@ContextParam(ContextType.TRIGGER_EVENT) Event event) { }

當然,可以指定 child class、也可以兩個同時一起用。

Layout 之謎

listboxauxhead 中,各個 component 要(視覺上合理地)撐滿, 學理上給 width="100%" 或是 hflex="1",當然以 render 速度來說應該設 width 會比較快,不過:

  • textbox:只能用 hflex="1"

  • combobox:都可以

Component

Borderlayout

margins 的順序居然是「上、左、右、下」

setCollapsible() 的前提條件是 getTitle 必須有值。

Cell

用了 Cell 會讓 DOM 結構與原本(不用 Cell)的 DOM 結構不一樣 ref。 這就會導致 Grid 原本賦予的 style 掛不上去。 解決方法大概有這幾種:

  1. 每個 Row 裡頭 component 統一都用 Cell 包起來。

  2. 寫一個 global 的 CSS 對應

  3. 不管什麼 deprecated,繼續用 Row.setSpans()

結果似乎最保險的是最後那個不管 deprecated... 真是幹他媽的好 ZK 阿...

Combobox

Combobox.setValue() 是對應選到的 Comboitem.getLabel()Comboitemvalue 目前看起來無意義。

Datebox

如果只是要借用 Datebox 顯示、而不讓使用者修改值,那除了 setReadonly(true) 之外還要加上 setButtonVisible(false)

Listbox

row 會蓋掉 vflex 的設定。不過如果有觸發 resize,那 vflex 的設定還是會再蓋回來。

auxheader 才能作 colspan,listheader 不行; 要出現 auxheader 必須要有 listhead(空的也無所謂)。 auxheader 設定寬度無效,會依照 listheader 的設定跑。

auxhead 如果塞 textbox 之類的元件,建議下 hflex="1" 會比 width="100%" 好看。

目前找到畫面載入時預設全選的最簡單方法:

`onAfterRender` 的時候,資料已經 ready 了(先不考慮動態載入資料的問題 Zzzz), 所以 `selectAll()` 可以運作。 在 EE 版,一個有設定 `rod=true` 的 Listbox 是不會出現 select all 的 checkbox。 如果不是在 EE 版,設定 `rod=true`(無論在 zk.xml 還是 ZUL)都會被忽略掉, 所以一定會出現 select all 的 checkbox。 ### nonselectableTags ### Listbox 中的某些 **HTML tag** 的 `onClick` 行為,天生就不會連帶觸發 Listbox 的 `onSelect` 行為, 例如 ``、``、``、``。 然後透過 `nonselectableTags` 這個 attribute,可以重新決定哪些 **HTML tag** 會 / 不會觸發 `onSelect`。 例如希望圖片跟按鈕都不要觸發 `onSelect`,就要這樣寫

nonselectableTags 也可以給 *,表示不管阿貓阿狗都不會觸發 onSelect。 然後建議搭配 checkmark="true"

最後,請注意,nonselectableTags 接受的值是幹他媽的 HTML tag, 不是 ZUL component 名稱,也就是說,你得知道哪些 component 實際上是由什麼 HTML 湊出來的。

Macro

macro 似乎會預設兩個 style、其中一個是 display:inline-block。 如果對 macro 作 setVisible(false) 之後再 setVisible(true), 這個 macro 的 style 就會被清光光...... WTF

Textbox

ZUL 的 onOK 是按下 enter 時候會做的事情。

如果設定 rows/cols,那麼就會無視 width/height/hflex/vflex 的設定, Textbox 的大小會直接以 rows/cols 為準。 所以如果希望 Textbox 是 liquid(大小隨 parent 的大小而變動) 就是設定 multiline="true",然後不要給 rows/cols

Textbox 很容易被 parent 截掉、但是視覺上看不出來(例如在 Grid 當中)。 只能說當 Textbox 是多行狀態(等同於 HTML 的 textarea), 在 Chrome / Firefox 當中,右下角都會出現可以調整大小的 button, 以此作為判斷依據,也可以避免發生「怎麼都沒有 scroll bar」的誤判狀況 Orz。

Tree

如果要知道一個 multiple 的 Tree (不是 multiple 的沒有這個問題,用 getSelection() 就好) 剛剛點選 / 取消點選哪個 node, 無法直接從 API 取得,必須要:

  1. ZUL 的 treeitem 這樣寫

  2. treeonSelect 在 VM 是:

     public void treeSelect(@ContextParam(ContextType.TRIGGER_EVENT) SelectEvent<Treeitem, Foo> se) {
         @SuppressWarnings("unchecked")
         TreeNode<Foo> node = (TreeNode<Foo>)se.getReference().getValue();
     }

ZK 7.0 似乎有新增相關的 API:getPreviousSelectedObject(),還未測。

Window

要有設定 title 的情況下 closable="true" 才會有效果... =="

Last updated