頁面導航是指在一個應用程式內「多個頁面之間切換」的議題。當頁面越來越多的時候,就需要一個方法將頁面組織串連起來,好讓使用者可以容易在多個頁面之間遊走,不致迷失。
頁面數量不多的時候,可以就用以往整切換的方式,例如用 <a>
直接指向目的頁面:
<a href="mypath/page.zul"/>
也可以在按鈕的 onClick listener 中呼叫頁面重導向:
Executions.getCurrent().sendRedirect("mypath/mypage.zul");
Execution
是一個封裝 Http request 的物件,你可以把它當作 request 來看。Executions.getCurrent()
回傳當前的 Execution
,要在 Servlet thread 中呼叫才能取得,例如 event listener 中但是整頁切換就會有過往 JSP 的缺點:換頁速度較慢、整頁重傳會有許多重複的內容。因此 ZK 中比較建議採用部分頁面切換的方法,頁面網址維持不變,只有抽換內容,也類似單頁式應用程式 (Single Page Application)的做法。以最常見的排版為例,左邊為「選單側邊欄」,右邊為「內容區」。使用者點選側邊欄後,只有內容區會切換。
右邊內容區不是寫死的固定內容,而是放入一個 <apply>
元素,用來動態插入頁面。
先做側邊欄,我預計是每個選單項目有一個 key 值,可以用該值來推算出對應的頁面名稱:
nav-template.zul
<navbar orient="vertical" width="200px" >
<navitem label="財務" iconSclass="z-icon-book" selected="true">
<custom-attributes name="finance"/>
</navitem>
<navitem label="管理" iconSclass="z-icon-user">
<custom-attributes name="management"/>
</navitem>
<navitem label="研究" iconSclass="z-icon-lightbulb-o">
<custom-attributes name="research"/>
</navitem>
</navbar>
<cutom-attributes>
是用來存資料的元素,放在元件內就等同於呼叫該元件 setAttribute(key, value)
,因此我用來存入一個 key 為 name, value 為 finance 的資料。為了簡單化,這個值就等於該選單的對應頁面名稱,因此對應的頁面就是 finance.zul
<hlayout height="100%" apply="quickstart.nav.TemplateNavComposer">
<navbar orient="vertical" width="200px" >
...
</navbar>
<div sclass="content" hflex="1" vflex="1" ...>
<apply id="content" templateURI="finance.zul" dynamicValue="true"/>
</div>
</hlayout>
dynamicValue="true"
public class TemplateNavComposer extends SelectorComposer<Component> {
@Wire("::shadow#content")
private Apply contentTemplate;
@Wire
取得 shadow 元素的參照@Listen(Events.ON_SELECT+ "= navbar")
public void navigate(SelectEvent event){
//取得頁面名稱
String pageName = ((Navitem)event.getSelectedItems().iterator().next()).getAttribute("name").toString();
// 換頁面
contentTemplate.setTemplateURI(pageName + ".zul");
contentTemplate.recreate();
}
onSelect
事件。點選已經選的選單就不會再發了,因此不會重複載入同一頁面。getAttribute("name")
來取得每個選單的存的 key 值,透過該值推算出頁面名稱setTemplateURI()
切換內容區頁面名稱,並重建該部分內容當使用者點選一個項目就產生一個新分頁的導航方式也是很常見的一種。
假設仍是分成左右兩區,左邊側邊欄跟前一個例子相同。右邊換成一個空的 <tabbox>
nav-tab.zul
<hlayout height="100%" apply="quickstart.nav.TabNavComposer">
<navbar orient="vertical" width="200px" >
...
</navbar>
<div sclass="content" hflex="1" vflex="1">
<tabbox vflex="1">
...
</tabbox>
</div>
</hlayout>
Tabbox 也是一個支援 Model-driven rendering 的元件,因此我可以賦予一個 ListModelList
,動態的增減 tab 數量:
public class TabNavComposer extends SelectorComposer {
@Wire("tabbox")
private Tabbox tabbox;
private ListModelList<TabState> tabModel = new ListModelList();
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
tabbox.setModel(tabModel);
}
我假定每個 tab 上的圖示、名稱各有一套設定,並存在 TabState
中。我先製作了3 組樣本設定:
private static Map<String, TabState>tabStates
= Map.of("finance", new TabState("財務", "z-icon-book"),
"management", new TabState("管理", "z-icon-user"),
"research", new TabState("研究", "z-lightbulb-o"));
需要定義兩個範本,分別給 <tab>
跟 <tabpanel>
:
<tabbox id="box" vflex="1">
<template name="model:tab">
<tab iconSclass="${each.iconClass}">
${each.name}
</tab>
</template>
<template name="model:tabpanel">
<tabpanel>
${each.name}
</tabpanel>
</template>
</tabbox>
${each}
參照到 TabState
物件,因為我的資料模型中是存放 TabState
@Listen(Events.ON_CLICK+ "= navitem")
public void navigate(MouseEvent event){
//取得頁面名稱
String pageName = ((Navitem)event.getTarget()).getAttribute("name").toString();
addTab(pageName);
}
onClick
而不是前例的 onSelect
public void addTab(String pageName){
try {
tabModel.add((TabState)tabStates.get(pageName).clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
TabState
) 加入 tabModel
中,ZK 就會幫我畫出新的 tab刪除 tab 也必須要透過操作 ListModelList
。
<tabbox id="box" vflex="1">
<template name="model:tab">
<tab iconSclass="${each.iconClass}" closable="true" forward="onClose=box.onClose(${each})">
${each.name}
</tab>
</template>
...
</tabbox>
closable
能在 tab 上啟動關閉功能${each}
作為參數傳入,這樣就可得知 Tab 所對應 TabState
@Listen("onClose = #box")
public void closeTab(ForwardEvent event){
TabState tabState = (TabState) event.getData();
tabModel.remove(tabState);
}
event.getData()
取得轉發事件時傳入的 ${each}
remove()
移除對應的 tabState
之後,tabbox 就會自動幫我們把瀏覽器上的 tab 移除如果要在同一個頁面設定不同狀態例如
請參考 Browser History Management
如果是要讓使用者可在頁面內跳至不同的位置,請使用元件 Anchornav