页面导航是指在一个应用程序内「多个页面之间切换」的议题。当页面越来越多的时候,就需要一个方法将页面组织串连起来,好让使用者可以容易在多个页面之间游走,不致迷失。
页面数量不多的时候,可以就用以往整切换的方式,例如用 <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
指令的部分终於完结了! 今天就来做 Logger 吧, 目标是要有一致性和一定程度的可读性, 让之後...
iOS工程师面试深入浅出(OC)- @property 使用方法?Copy 什麽时候用? 如果本来是...
台湾的特殊位置,使她在夏秋之间常遭受台风袭击,但偶尔也会有搞错季节的晚台。 诗忆望着窗外灰暗的天空,...
在经历上一部函数与类别的摧残後,这两天就来教一些比较温和的程序吧~ 今天的影片内容为介绍常见的档案格...
发牌员 我们的短期目标是,在网页上用纯文字的方式直接显示游戏状态 并会随着游戏更新的时候更新 原本想...