Wire Variables"

From Documentation
Line 13: Line 13:
  
 
=Wire Spring-managed Beans=
 
=Wire Spring-managed Beans=
Basically there are two approaches depending on whether you want to make the composer as a Spring-managed bean.
 
  
==Approach 1: make the composer as a Spring-managed bean==
+
<javadoc>org.zkoss.zk.ui.util.GenericForwardComposer</javadoc> not only wires UI components, but also wires beans that can be found by the registered variable resolvers. Thus, if you'd like <javadoc>org.zkoss.zk.ui.util.GenericForwardComposer</javadoc> to wire the Spring-managed beans, you could register the Spring variable resolver, <javadoc>org.zkoss.zkplus.spring.DelegatingVariableResolver</javadoc>. Then, if a data member's name matches a Spring-managed bean, it will be wired automatically too. For example,
 
 
If you prefer to manage composers with Spring, you could simply declare the Spring variable resolver called <javadoc>org.zkoss.zkplus.spring.DelegatingVariableResolver</javadoc> in a ZUML document, and then use EL to retrieve the composer in the apply attribute. For example,
 
 
 
<source lang="xml">
 
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
 
<window apply="${passwordSetter}"> <!-- assume there is a Spring-managed bean called passwordSetter -->
 
...
 
</source>
 
 
 
As shown above, <tt>passwordSetter</tt> shall be a Spring-managed bean.
 
 
 
With this approach, since the composer will be instantiated by Spring, all the data member that Spring recognizes will be wired. Thus, you could access them directly.
 
 
 
<source lang="java" high="4,6">
 
import org.springframework.stereotype.Component;
 
import org.springframework.beans.factory.annotation.Autowired;
 
 
 
@Component
 
public class PasswordSetter extends GenericForwardComposer {
 
    @Autowired
 
    private User user; //wired automatically by Spring
 
    private Textbox password; //wired automatically by ZK
 
 
 
    public void onClick$submit() {
 
        user.setPassword(password.getValue());
 
    }
 
}
 
</source>
 
 
 
As shown,  (non-UI) beans are wired by Spring, so you have to specify Spring's annotation accordingly, such <code>@Component</code> to annotate the composer and <code>@Autowired</code> to the field.
 
 
 
==Approach 2: have GenericForwardComposer to wire a Spring-managed bean==
 
 
 
If you prefer not to have Spring to manage composers (for better separation or other reasons), you could have <javadoc>org.zkoss.zk.ui.util.GenericForwardComposer</javadoc> to wire it for you.
 
 
 
<javadoc>org.zkoss.zk.ui.util.GenericForwardComposer</javadoc> not only wires UI components, but also wires beans that can be found by the registered variable resolvers. Thus, you could have ZK to wire the Spring-managed beans by registering the Spring variable resolver manually as shown in the following code snippet. Then, if a data member's name matches a Spring-managed bean, it will be wired automatically too. For example,
 
  
 
<source lang="java" high="2,6">
 
<source lang="java" high="2,6">
Line 61: Line 24:
 
         page.addVariableResolver(new org.zkoss.zkplus.spring.DelegatingVariableResolver());
 
         page.addVariableResolver(new org.zkoss.zkplus.spring.DelegatingVariableResolver());
 
             //variable resolver must be added before calling super.doAfterCompose
 
             //variable resolver must be added before calling super.doAfterCompose
            //since the autowiring depends on it
+
         super.doAfterCompose(comp); //invoke autowiring
         super.doAfterCompose(comp);
 
 
     }
 
     }
 
     public void onClick$submit() {
 
     public void onClick$submit() {
Line 71: Line 33:
  
 
As shown, we could register the variable resolver in <code>doAfterCompose</code> by use of
 
As shown, we could register the variable resolver in <code>doAfterCompose</code> by use of
<javadoc method="addVariableResolver(org.zkoss.xel.VariableResolver)">org.zkoss.zk.ui.Page</javadoc>.
+
<javadoc method="addVariableResolver(org.zkoss.xel.VariableResolver)">org.zkoss.zk.ui.Page</javadoc><ref>Instead of registering it manually, you could use [[ZUML Reference/ZUML/Processing Instructions/variable-resolver|the variable-resolver directive]] in the ZUML document.</ref>.
 +
 
 +
{{references}}
 +
 
 +
==Warning: Not a good idea to have Spring managing the composer==
 +
There is a tendency to make the composer as a Spring-managed bean. For example, assume we have a composer called <code>passwordSetter</code> and managed by Spring (i.e., annotated with Spring's <code>@Component</code>), then we might do as follows.
 +
 
 +
<source lang="xml">
 +
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
 +
<window apply="${passwordSetter}">
 +
...
 +
</source>
 +
 
 +
<source lang="java">
 +
@Component
 +
public class PasswordSetter extends GenericForwardComposer {
 +
  @Autowired User user;
 +
...
 +
</source>
 +
 
 +
Unfortunately, this approach is error-prone. The reason is that none of Spring's scopes matches correctly with the lifecycle of the composers. For example, if the session scope is used, it will cause errors when the user opens two browser windows to visit the same page. In this case, the same composer will be used to serve all desktops in the given session.
 +
 
 +
If the prototype scope is used, another new instance will be instantiated if the Spring variable resolver is called to resolve the same name again in the later requests. It sounds unlikely, but it might be triggered implicitly and hard to debug. For example, it happens if some of your code, including data binder, evaluates an EL expression that references the composer's name, when an event received.
 +
 
 +
If you really want Spring to manage the composers, please use [http://www.zkoss.org/product/zkspring ZK Spring]. Please refer to [[ZK Spring Essentials]] for more detailed information.
  
 
=Wire CDI-managed Beans=
 
=Wire CDI-managed Beans=

Revision as of 02:48, 8 December 2011

Wiring Sequence

GenericForwardComposer will wire members defined in the composer. Here is the sequence:

  1. It searches any setters if its name matches any of the following
    1. Any fellow with the same name (Component.getFellow(String))
    2. Any variable defined in Page.addVariableResolver(VariableResolver) with the same name
    3. Any variable defined in zscript with the same name
    4. Any implicit object with the same name
  2. Then, it searches any field to see if it matches any fellow, any variable,... the same as the above.

Wire Spring-managed Beans

GenericForwardComposer not only wires UI components, but also wires beans that can be found by the registered variable resolvers. Thus, if you'd like GenericForwardComposer to wire the Spring-managed beans, you could register the Spring variable resolver, DelegatingVariableResolver. Then, if a data member's name matches a Spring-managed bean, it will be wired automatically too. For example,

public class PasswordSetter extends GenericForwardComposer {
    private User user; //wired automatically if user is a spring-managed bean
    private Textbox password; //wired automatically if there is a textbox named password

    public void doAfterComposer(Component comp) {
        page.addVariableResolver(new org.zkoss.zkplus.spring.DelegatingVariableResolver());
            //variable resolver must be added before calling super.doAfterCompose
        super.doAfterCompose(comp); //invoke autowiring
    }
    public void onClick$submit() {
        user.setPassword(password.getValue());
    }
}

As shown, we could register the variable resolver in doAfterCompose by use of Page.addVariableResolver(VariableResolver)[1].

Template:References

Warning: Not a good idea to have Spring managing the composer

There is a tendency to make the composer as a Spring-managed bean. For example, assume we have a composer called passwordSetter and managed by Spring (i.e., annotated with Spring's @Component), then we might do as follows.

<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
<window apply="${passwordSetter}">
...
@Component
public class PasswordSetter extends GenericForwardComposer {
   @Autowired User user;
...

Unfortunately, this approach is error-prone. The reason is that none of Spring's scopes matches correctly with the lifecycle of the composers. For example, if the session scope is used, it will cause errors when the user opens two browser windows to visit the same page. In this case, the same composer will be used to serve all desktops in the given session.

If the prototype scope is used, another new instance will be instantiated if the Spring variable resolver is called to resolve the same name again in the later requests. It sounds unlikely, but it might be triggered implicitly and hard to debug. For example, it happens if some of your code, including data binder, evaluates an EL expression that references the composer's name, when an event received.

If you really want Spring to manage the composers, please use ZK Spring. Please refer to ZK Spring Essentials for more detailed information.

Wire CDI-managed Beans

The approach to work with CDI is similar to the approach for Spring, except the variable resolver for CDI is DelegatingVariableResolver.

zscript and Variable Resolver

By default, variables defined in both zscript and variable resolvers will be checked[2]. There might be some performance penalty if you have too much zscript code, or your variable resolver is too slow.

You could turn them off by passing false to the second and third argument of GenericForwardComposer.GenericForwardComposer(char, boolean, boolean):

public class MyComposer extends GenericForwardComposer {
    public MyComposer() {
        super('$', false, false);
    }
}

Notice if you disable the wiring of EL variables (i.e., variables defined in a variable resolver), you can't wire the Spring-managed beans as described in the previous section.


  1. Instead of registering it manually, you could use the variable-resolver directive in the ZUML document.
  2. It will be default to false in ZK 6 for better performance.

ID Space

For components that are inside of another ID space, you could use id1$id2$id3 to access it. More precisely, it will cause GenericForwardComposer to look for id1 in the same ID space as the applied component, and then look for, if found and it is another ID space, id2, and so on. For example, you could find the textbox by inner$input.

<window apply="foo.MyComposer">
   <window id="inner">
        <textbox id="input"/>
...

Here is another example: suppose have two ZUL files:

Main file Included file - includeme.zul
<window id="mywindow" apply="MyComposer">
    <include id="i" src="includeme.zul" />
</window>
   <textbox id="username" />

To access the textbox "username" from "MyComposer", you could specify:

public class MyComposer extends GenericAutowireComposer {
    Textbox i$username;
    ...
}

Version History

Last Update : 2011/12/08


Version Date Content
     



Last Update : 2011/12/08

Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.