常规宏

ZK创建一个真实的组件(称为宏组件)来显示常规宏,即前一章节描述的。

为了描述方便,当在此章节中讨论的宏组件时,即意味着为常规宏组件。

宏组件和ID空间

就像window,宏组件为一个ID空间所有者。换言之,在实现宏组件(亦=宏组件的子组件)的页面内使用什么标识来标识组件是自由的。它们不会和定义在使用宏组件的同一页面内的组件相冲突的。

例如,假定我们有如下一个宏定义。

<hbox>
   Username: <textbox id="who" value="${arg.who}"/>
</hbox>

那么下面的代码将会正常工作。

<?component name="username" macro-uri="/WEB-INF/macros/username.zul"?>
<zk>
   <username/>
   <button id="who"/> <!-- no conflict because it is in a different ID space -->
</zk>

但是,下列的代码将不会工作。

<?component name="username" macro-uri="/WEB-INF/macros/username.zul"?>
<username id="who"/>

为什么呢?就像任何ID空间所有者,宏组件本身和其子组件在相同的ID空间内。有两种可选的解决方案:

  1. 为宏组件的子组件标识使用一个特殊的前缀。例如使用"mc_who"代替 "who"

<hbox>
   Username: <textbox id="mc_who" value="${arg.who}"/>
</hbox>

  1. 使用window 组件创建 一个额外的ID空间。

<window>
   <hbox>
      Username: <textbox id="who" value="${arg.who}"/>
   </hbox>
</window>

将以使用第一种方案T,简单方便。

从外部访问子组件

就像其它的ID空间所有者,你可以调用getFellow方法或使用 org.zkoss.zk.ui.Path来访问其子组件(by use of two getFellow method invocations or org.zkoss.zk.ui.Path)。

例如,假定你有一个ID为"username"的宏组件,那么可以按如下方式访问textbox。

comp.getFellow("username").getFellow("mc_who");
new Path("/username/mc_who");

访问定义在Ancestor的变量

宏组件像内联扩展一样工作。因此,就像其它组件,(一个宏组件的)子组件可以访问任何定义在父组件ID空间内的变量。

例如,username的子组件可以直接访问v

<zscript>
   String v = "something";
</zscript>
<username/>

但是,并不推荐这样使用(it is not recommended to utilize such visibility),因为这或许会限制宏的使用范围。

运行时改变macro-uri

你可以动态的改变宏的URI,如下。

<username id="ua"/>
<button onClick="ua.setMacroURI(&quot;another.zul&quot;)"/>

增设方法

宏组件实现了org.zkoss.zk.ui.ext.DynamicPropertie接口,所以你可以按如下方式使用 getDynamicProperty方法访问其属性。

<username id="ua" who="John"/>
<button label="what?" onClick="alert(ua.getDynamicProperty(&quot;who&quot;))"/>

显然使用Dyn amicPropertied是很繁琐的。更糟的是,若你使用setDynamicProperty改变一个属性,却不会被改变宏的子组件。例如,下面的代码会显示John作为username,而不是Mary

<username id="ua" who="John"/>
<zscript>
  ua.setDynamicProperty("who", "Mary");
</zscript>

为什么呢?一个宏组件的所有子组件都是在创建宏组件时被创建的,除非你手动操作这些子组件[58],否则是不会改变它们的。调用setDynamicProperty仅会影响存储在宏组件中的属性(可以使用getDynamicProperties 取的)。textbox的内容仍没有改变。

因此,最好增设一个方法,例如setWho,用以直接操作宏组建组件。为了增设你自己的方法,必须为宏组件实现一个class,然后使用component 指令的class属性指定此类。

[提示]: 可以使用recreate方法来再创建(recreate) 子组件(包括其当前的属性)。实际上此方法是移除了所有的子组件,然后再重新创建它们。

有两种方式实现一个类。下面的章节描述了细节。

在java中增设方法

为宏组件增设方法需要两个步骤。

  1. 继承org.zkoss.zk.ui.HtmlMacroComponent实现一个类。

    //Username.java
    package mypack;
    public class Username extends HtmlMacroComponent {
       public void setWho(String name) {
          setDynamicProperty("who", name); //arg.who requires it
          final Textbox tb = (Textbox)getFellow("mc_who");
          if (tb != null) tb.setValue(name); //correct the child if available
       }
       public String getWho() {
          return (String)getDynamicaProperty("who");
       }
    }
    
    • 正如上面所描述的,你必须在setWho内调用setDynamicProperty,因为在宏页面(${arg.who})内引用了 ${arg.who}${arg.who}被用于宏组件创建其子组件时。

    • 由于setWho方法或许会在宏组件创建其子组件之前被创建,因此你必须检查mc_who是否存在。

    • 由于调用了mc_whosetValue,当调用setWho时,客户端的内容及视觉表现(visual presentation)都会被自动更新。

  2. 使用class属性在宏声明内声明类。

<?component name="username" macro-uri="/WEB-INF/macros/username.zul"
   class="mypack.Username"?>

zscript中增设方法

除了使用Java文件实现,你也可以在zscript中实现Java class(es)。优点是不需要编译(compilation),并且可以动态的修改其内容(无需重新部署Web应用程序)。缺点是降低了性能且容易出现打字错误(prone to typos)。

需要几个步骤在zscript中实现Java class 。

  1. 你需要为要实现的类准备一个zscript 文件,例如/zs/username.zs。注意你可以在相用的zscript 文件内放置任意数量的类及函数。

    //username.zs
    package mypack;
    public class Username extends HtmlMacroComponent {
       public void setWho(String name) {
          setDynamicProperty("who", name);
          Textbox tb = getFellow("mc_who");
          if (tb != null) tb.setValue(name);
       }
       public String getWho() {
          return getDynamicProperty("who");
       }
    }
    
  2. 使用init指令加载zscript文件,然后声明组件。

    <?init zscript="/zs/username.zs"?>
    <?component name="username" macro-uri="/WEB-INF/macros/username.zul"
       class="mypack.Username"?>
    

实现类(前一个例子中的mypack.Username)直到宏组件被使用时才会被决定(resolved),所以使用zscript元素为zscript文件赋值(evaluate)是没有问题的。

<?component name="username" macro-uri="/WEB-INF/macros/username.zul"
   class="mypack.Username"?>
<zk>
   <zscript src="/zs/username.zs"/>
   <username/>
</zk>

尽管主观(subjective),init指令仍更具有可读性。

当实例化时重写实现类

就像其它的任何组件,你可以为任何特定的实例使用use属性重写用于实现宏组件的类。

<?component name="username" macro-uri="/WEB-INF/macros/username.zul"
   class="mypack.Username?>

<username use="another.MyAnotherUsername/>

当然在上面的例子中,你必须要提供一个another.MyAnohterUsername的实现。然后再一次,可以使用单独的Java文件或zscript来实现此类。

手动创建一个宏组件

为了手动创建一个宏组件,你必须在所有的初始化完成后调用afterCompose方法,如下。

HtmlMacroComponent ua = (HtmlMacroComponent)
   page.getComponentDefinition("username", false).newInstance(page);
ua.setParent(wnd);
ua.applyProperties(); //apply properties defined in the component definition
ua.setDynamicProperty("who", "Joe");
ua.afterCompose(); //then the ZUML page is loaded and child components are created

[注]: getComponentDefinition方法被用于在一个页面内查到组件定义。

若你为宏实现了一个类,例如Username,那么可按如下方式处理。

Username ua = new Username();
ua.setWho("Joe");
ua.setParent(wnd);
ua.afterCompose();



[58] 另外,在提交(rendering)阶段,include组件包含的子组件被创建。而且,每一次使include组件无效时,所有的子组件都会被移除并且创建。