模组化、重用使用者介面

重用页面片段

当我们设计页面发现有些元件的组合重复出现时,虽然可以直接「复制—贴上」,但是如果使用范本插入,会更符合物件导向设计中的「重用」原则 (或 DRY)。而且未来若是范本修改,所有插入范本的页面也都自然跟着更新,若是原本采用「复制—贴上」,就得一个一个页面去搜寻修改。

假设我们发现 <textbox> + <button> 这个 pattern 经常出现,就可以把它定义成一个范本并指定一个不重复的名字,光是定义范本并不会显示在页面上,而是要用 <apply> 插入。

https://ithelp.ithome.com.tw/upload/images/20210930/20050621uVfss9SjM7.jpg

<apply template="one-field"/>

<template name="one-field">
    <textbox/><button label="送出" style="margin-left: 5px"/>
</template>

范本可以定义在页面上任何地方,<apply> 都可以透过指定范本名称来插入,ZK 会将范本内定义的片段插入到 <apply> 的位置。

模组化范本

若我事先在范本内指定几个 EL 变数,插入的时候根据不同的情境再传入不同的值的话,可使范本可应用的情境更广,变得更模组化。

例如我可以把「提示讯息」、「按钮文字」作为参数输入,这样每次使用时该范本时,我都可以调成不同的文字:
https://ithelp.ithome.com.tw/upload/images/20210930/20050621lItCEiWg6T.jpg

<apply template="one-field2" hint="请输入:" label="OK"/>

<template name="one-field2">
    ${hint}<textbox/><button label="${label}" style="margin-left: 5px"/>
</template>
  • 以上插入范本时传入两个参数: hint, label,对应到范本内的2个变数。不过这种方式传的参数并没有编译器帮你检查名称或数量,要自己检查避免打错字

模式切换

同一份资料有时候有不同的呈现方式,例如个人资料,可以是「显示模式」或「编辑模式」,就可以透过切换两种不同的范本来做到模式切换效果。

个人资料编辑

假设我要在呈现英雄的个人资料画面做出可以立即切换成编辑模式的编辑器:
阅读模式
https://ithelp.ithome.com.tw/upload/images/20210930/20050621hrPX7QyJsU.jpg

编辑模式
https://ithelp.ithome.com.tw/upload/images/20210930/20050621eBSQ2mZ9LE.jpg

首先先定义两种范本:normal, edit

<template name="normal">
    <label value="${hero.id}"/>
    <label value="${hero.name}"/>
    <label value="${hero.age}"/>
</template>
<template name="edit">
    <label value="${hero.id}"/>
    <textbox id="nameBox" value="${hero.name}" width="100%"/>
    <intbox id="ageBox" value="${hero.age}"/>
</template>
  • 范本内用 EL 存取 Java bean,两者都显示3项相同的 property,只是编辑模式范本内的要改成输入元件

再透过按钮去切换不同的范本:

<div  style="width: 50%" apply="quickstart.shadow.ProfileEditorComposer">
    <button label="Edit"/>
    <vlayout style="border: solid 2px; border-radius:5px; width: 150px; padding:5px; margin: 5px">
        <apply id="profile" template="normal" hero="${hero}" dynamicValue="true"/>
    </vlayout>
</div>
  • 因为范本之後要动态被改变,因此 dynamicValue 要设成 true

控制器实作解说

public class ProfileEditorComposer extends SelectorComposer {
    @Wire("::shadow#profile")
    private Apply apply;
  • shadow element 也可以用 @Wire 来取得参考,selector 语法为 ::shadow 代表所有的 shadow 元件,加上 # ID 选择器,代表 ID 为 profile 的 shadow 元件
@Override
public void doBeforeComposeChildren(Component comp) throws Exception {
    super.doBeforeComposeChildren(comp);
    hero = HeroService.create("奇异博士");
    Sessions.getCurrent().setAttribute("hero", hero);
}
  • doBeforeComposeChildren() 也是一个生命周期方法,根据名称就可得知它是在子元件被建构前被呼叫。这次我覆写 doBeforeComposeChildren() 是因为我要在范本中的元件产生前就把资料准备好,这样 EL 才能存取到 attribute。
  • Sessions.getCurrent() 是 ZK 提供的方法,让我可以取得现有 request 所属的 session。请注意我存入的键值为 hero,也就是我在 zul 上用 EL 存取的变数名。
<apply id="profile" template="normal" hero="${hero}" dynamicValue="true"/>
  • 这里用 ${hero} 把参数传入范本,那这个变数定义在哪呢?ZK 解析 EL 变数时,会从较小的范围(scope) 一直解析到大的范围,因此若是 zscript 中没有定义 hero 这个变数,ZK 会持续在 session, application 中的 attribute 寻找 key 值为 hero 的物件

实作点击按钮来切换模式

private boolean isEdit = false;

@Listen("onClick = button")
public void switchMode(){
    if (isEdit){
        hero.setName(((Textbox)modeButton.getFellow("nameBox")).getValue());
        hero.setAge(((Intbox)modeButton.getFellow("ageBox")).getValue());
    }
    isEdit = !isEdit;
    modeButton.setLabel(isEdit ? "Save": "Edit");
    apply.setTemplate(isEdit ? "edit" : "normal");
    apply.recreate();
}
  • @Listen 注册 onClick 倾听器
  • 我用 setTemplate() 来切换不同的范本,并呼叫 recreate() 来重建范本内容

页面布局

另一种应用是将范本定义为一个布局范本,然後在每个页面中插入,可以使得页面的布局一致。例如我设计一个由分成标头、内容、页尾3部分的布局范本:

https://ithelp.ithome.com.tw/upload/images/20210930/20050621vdxdAB3Y7u.jpg

layout-template.zul

<borderlayout height="100%">
    <north size="20%" style="background-color:#5c5480">
        <div sclass="center">header</div>
    </north>
    <center style="background-color:#738096">
        <apply template="content"/>
    </center>
    <south size="5%" style="background-color:#5c5480">
        <div sclass="center">footer</div>
    </south>
</borderlayout>
  • 标头、页尾都是固定的内容
  • 中间内容没有定义,插入一个尚未定义的范本 content

当要插入该布局范本时,才定义 content 这个范本的内容:

<apply templateURI="layout-template.zul">
    <template name="content">
        <div sclass="center">本页面内容</div>
    </template>
</apply>

这样就可以做到,既维持页面在同一个3分布局,又能弹性决定每个页面的内容。

以上几个例子可以帮你更好的重用你的页面,避免「复制—贴上」这种容易产生 bug 的行为。


<<:  [第十五只羊] 迷雾森林舞会IX 玩家加入房间

>>:  新新新手阅读 Angular 文件 - pathMatch(4) - Day30

【C# 群益 API 开发教学】取得商品报价、Tick、最佳 5 档教学 #CH3 (附范例)

群益 API 是利用自己开发的程序,结合群益 API 在群益券商下单的一种方式,通常是做程序交易下单...

【day14】 Fab X 跳页Animation

好的! 我们今天主要要来设定Fab,以及跳页的动画! 1.先在我们的DashboardFragme...

就先在这边帮他上了一个主题了 Day1

今天是自己开始铁人赛的第一天,目标其实很简单,想要把之前学到的,之前没学到的 全部整合整理成一个作品...

强制访问控制(MAC)- 安全许可(Security clearance)

-安全内核 一张图片胜过千言万语。访问控制矩阵可以被视为授权数据(权利和许可)的逻辑“存储库”,由...

Progressive Web App 开箱看看渐进式增强到底有多强 (1)

Progressive Web App (PWA) 使用当下最新潮的 Web API,Progres...