shadow element, 它的命名就透露出它不是個外顯的 UI 元件,實際上它的確不會繪製出任何東西到瀏覽器上,只是在元件樹上控制元件的生成與消滅,如同陰影般隱藏在元件背後的幕後操盤手。以下方的片段來說:
<div>
<if test="${user.editable}">
User Name: <textbox value="${user.name}"/>
<forEach items="${user.phones}" var="phone">
<label value="${phone.number}"/>
</forEach>
</if>
</div>
解析完 zul 之後會產生左邊的元件樹。
執行期時,會根據 <if>
中 EL 表達式算出來的結果來決定要不要生成其下的 <textbox>
<label>
。<forEach>
會根據 ${user.phones}
數量來決定要產生幾個 。而 shadow 元件本身並不會產生對應的元件實體,瀏覽器端也不會繪製出任何 DOM 元素。
Shadow 元件共有以下這些:
<apply>
: 範本插入,可以插入事先定義的範本或是 zul 檔<forEach>
:走訪集合物件來控制元件生成<if>
:條件判斷,如果給定的 EL 結果為 true 就新建其包含的元件,如果為 false 就移除其包含的元件<choose>
<when>
<otherwise>
:條件判斷,如同 java 中的 switch, case, default在我們用 zul 建構畫面時,有時會發現某些元件組合或是某個頁面的區塊重複出現,最直接的方式是「複製—貼上」,但更好的方式能夠直接重用該片段。ZK 以往提供的方式是用 ,8.0 之後則可使用 shadow 元件 <apply>
,效果更好。
我推薦優先使用 <apply>
,理由如下:
<apply>
不會產生任何 DOM 元素<include>
會產生一層 <div>
,有時候多一個 <div>
會影響畫面排版<apply>
不會佔用記憶體<include>
本身是 UI 元件,因此本身仍會佔用記憶體<apply>
不會產生 ID space。<include>
會產生一個 ID space,導致要 @Wire
元件時要 selector 語法較複雜<apply>
可以接受檔案路徑或範本名稱<include>
只能接受路徑<apply>
就如同 inline 的概念一樣,由 ZK 把要引入的 zul 片段,由 zk 幫你動態的插入到目的頁面上。
如果填入字串值或是 EL,是屬於靜態用法,因為只在元件創建的時候估值,創建完之後伺服器就不需要保留 <apply>
物件,因此 <apply>
本身不佔記憶體:
<apply templateURI="customerDetails.zul" />
可透過 EL 來加上簡單的判斷邏輯:
<apply template="${currentUser.hasEditPermission ? 'editable' : 'readonly'}">
<template name="readonly">
<label value="${person.name}"/>
</template>
<template name="editable">
<textbox value="${person.name}"/>
</template>
</apply>
如果想要在控制器中,使範本重新創建其下的元件時,就要設定 dynamicValue="true"
,之後在範本內容變更時,可呼叫 Apply.recreate()
重新創建元件來更新瀏覽器畫面。
假如 ${currentUser.hasEditPermission}
有可能會變化,我希望能更新畫面,就要這麼寫:
<apply id="personBox"
template="${currentUser.hasEditPermission ? 'editable' : 'readonly'}"
dynamicValue="true">
<template name="readonly">
<label value="${person.name}"/>
</template>
<template name="editable">
<textbox value="${person.name}"/>
</template>
</apply>
${currentUser.hasEditPermission}
已經變了,只要呼叫 recreate()
來重建範本,就能在 <label>
跟 <textbox>
間切換