0

ZK-JSP and data binding

asked 2011-03-07 09:06:55 +0800

jarnisb gravatar image jarnisb
36 1

Hey Guys

I'm fairly new to ZK still, please forgive me if my question is very basic or my general method is way off. Here goes:

I'm trying to use autowiring and data binding from zk tags in a jsp page. Specifically, I'm trying to bind a combobox to a list instantiated in a GenericAutowireComposer. I would like to avoid using zscript entirely if possible. However, so far only the list populated in zscript (mammal) contain any items while the one populated in the GenericAutowireComposer class (bird) is empty.

The jsp page:

<%@ page language="java" errorPage="/error.jsp" pageEncoding="UTF-8" contentType="text/html;charset=utf-8" %>
<%@ include file="/common/taglibs.jsp" %>
<%@ taglib prefix="zk" uri="http://www.zkoss.org/jsp/zul" %>
<link rel="stylesheet" type="text/css" href="<c:url value='/zkau/web/a5f28083/zul/css/zk.wcs'/>"/>

<zk:init use="org.zkoss.zkplus.databind.AnnotateDataBinderInit" />
<zk:init use="dk.telmore.app.webapp.action.admin.TestAdminHandler" />

<zk:page>
    <zk:zscript>
        List mammalList = new ArrayList();
        mammalList.add("Dog");
        mammalList.add("Elephant");
        mammalList.add("Lion");
        ListModel mammalModel = new org.zkoss.zkplus.databind.BindingListModelList(mammalList, true);
    </zk:zscript>

    <zk:window id="zkWin" title="Test"
               apply="dk.telmore.app.webapp.action.admin.TestAdminHandler"
               border="normal" width="100%" height="100%">

        Mammal: <zk:combobox id="comboboxMammal" model="@{mammalModel}" width="99%" />
        Bird: <zk:combobox id="comboboxBird" model="@{birdModel}" width="99%" />

    </zk:window>
</zk:page>

Composer:

public class TestAdminHandler extends GenericAutowireComposer implements Initiator {

    private List<String> birdList;
    private ListModel birdModel;

    private Window zkWin;
    private Combobox comboboxBird;

    private AnnotateDataBinder binder = null ;

    @Override
    public void doAfterCompose(Component comp){
        try {
            super.doAfterCompose(comp);
        } catch (Exception e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        initModel();
        comboboxBird.setModel(birdModel);

        if (binder != null){
            binder.loadAll();
        } else {
            System.out.println("binder == null");
        }
    }

    public void doAfterCompose(Page page) throws Exception {
        System.out.println("DoAfterCompose(page)");
    }

    public void doInit(Page page, Map args) throws Exception {
        System.out.println("init");
    }

    private void initModel() {
        birdList = new ArrayList<String>();
        birdList.add("Eagle");
        birdList.add("Chicken");
        birdList.add("Robin");
        birdModel = new BindingListModelList(birdList, false);
    }

}

I have omitted all import statements as well as getters/setters, but all fields have both a getter and setter method.

The "AnnotateDataBinder binder" has a null value in both doAfterCompose() and doInit(). As far as I can tell, it should be instantiated by the <zk:init use="org.zkoss.zkplus.databind.AnnotateDataBinderInit" /> tag, but it's not. What am I doing wrong? Is the problem related to wrong use of init, wrong class or content in ListModels, wrong way to use composer or some other problem/misunderstanding?

I'll greatly appreciate any suggestions. Thanks in advance,
Jarnis Bertelsen
Developer
Telmore A/S

delete flag offensive retag edit

7 Replies

Sort by ยป oldest newest

answered 2011-03-07 12:09:49 +0800

jarnisb gravatar image jarnisb
36 1

I have been fideeling around with the code for some hours and I found a working solution. However,my solution raises a bunch of new questions which I'd appreciate an expert view on. First the solution:
The test.jsp file has no significant changes. I have highlighted the changed parts:
public class TestAdminHandler extends GenericAutowireComposer implements Initiator {

private List<String> birdList;
private ListModel birdModel;

private Window zkWin;
private Combobox comboboxBird;

private AnnotateDataBinder binder = null ;

public TestAdminHandler(){
System.out.println("constructor called");
}


@Override
public void doAfterCompose(Component comp){
System.out.println("DoAfterCompose(component)");
try {
super.doAfterCompose(comp);
} catch (Exception e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}


// initModel();
comboboxBird.setModel(birdModel);
}

public void doAfterCompose(Page page) throws Exception {
System.out.println("DoAfterCompose(page)");
if (binder == null){
binder = new AnnotateDataBinder(page);
}
binder.bindBean("birdModel", birdModel);

binder.loadAll();
}

public void doInit(Page page, Map args) throws Exception {
System.out.println("init");
initModel();
}

private void initModel() {
birdList = new ArrayList<String>();
birdList.add("Eagle");
birdList.add("Chicken");
birdList.add("Robin");
birdModel = new BindingListModelList(birdList, false);
}

The questions:
TestAdminHandler's constructor is called twice. i assume it's one time because of the init tag and another from the windowtag as both these tags refer to TestAdminHandler. Does this in itself make it a stupid idea to combine Composer and Initiator? Are there other serious reasons to avoid this approach? Is there a workaround that will limit the constructor calls to one?

Why do I need 2 init tags (one for AnnotateDataBinderInit and another for TestAdminHandler)? Is there a way to combine these and is it advisable?

Why is there a doAfterCompose method for both page and component? What is the responsibility of each?

Are there any other things I do in a stupid way? Is doInit the right place to load/generate model data, or where could the initModel method be moved to? Am I doing anything twice while once would be enough? What kind of refactoring would make sense.

I'm happy I got my code to work, but I would really like a better understanding of the zk lifecycle events and some basic knowlegde about best-practice for the kind of thing I'm doing here.

Thanks in advance,
Jarnis Bertelsen

link publish delete flag offensive edit

answered 2011-03-13 20:56:31 +0800

samchuang gravatar image samchuang
4084 4

Hi

1. sample code for data binding with combobox

<?page title="new page title" contentType="text/html;charset=UTF-8"?>
<zk>
<zscript><![CDATA[
import java.util.*;
import org.zkoss.zk.ui.*;
import org.zkoss.zul.*;
import org.zkoss.zkplus.databind.*;
class Person {
	String _firstName = "";
	String _lastName = "";
	public void setFirstName(String firstName) {
		_firstName = firstName;
	}
	public String getFirstName() {
		return _firstName;
	}
	public void setLastName(String lastName) {
		_lastName = lastName;
	}
	public String getLastName() {
		return _lastName;
	}
	public void setFullName(String f) {
	}
	 
	public String getFullName() {
	       return _firstName + " " + _lastName;
	}
}

//controller
class MyController extends org.zkoss.zk.ui.util.GenericForwardComposer {
	Textbox firstName;
	Textbox lastName;
	Combobox combo;
	
	AnnotateDataBinder binder; 
	
	List persons;
	Person _seldPerson;
	
	public void doAfterCompose(Component comp) throws Exception {
		super.doAfterCompose(comp);
		
		int count = 10;
		persons = new ArrayList();
		for(int j= 0; j < count; ++j) {
		    Person personx = new Person();
		    personx.setFirstName("Tom"+j);
		    personx.setLastName("Hanks"+j);  
		    persons.add(personx);
		}
		
		
		//Note. we can also create our AnnotateDataBinder
		binder = new AnnotateDataBinder(comp);
		binder.bindBean("persons", persons);
		// and load data by 
		binder.loadAll();
	}
	
	public Person getSelectedPerson() {
		return _seldPerson;
	}
	
	public void setSelectedPerson(Person person) {
		_seldPerson = person;
	}
}
]]></zscript>

	<div id="ctrl" apply="MyController">
		<combobox id="combo" model="@{persons}"
			selectedItem="@{seldPerson}" value="@{seldPerson.fullName}">
			<comboitem self="@{each='person'}"
				label="@{person.fullName}">
			</comboitem>
		</combobox>

		<textbox id="firstName" value="@{seldPerson.firstName}" />
		<textbox id="lastName" value="@{seldPerson.lastName}" />
	</div>
</zk>

from the code, when you use model, you need this

			<comboitem self="@{each='person'}"
				label="@{person.fullName}">
			</comboitem>

you can also refer to The Concept of Data Binding

link publish delete flag offensive edit

answered 2011-03-13 21:05:31 +0800

samchuang gravatar image samchuang
4084 4

2. refer to javadoc

it mentioned


Set the AnnotateDataBinder instance as a custom attribute with the name as specified in arg2 (default to "binder") and store it in the component as specified in arg0 "component-path".(if arg0 is not specified, use Page instead.)

so, by default, you can access it by

<?page title="new page title" contentType="text/html;charset=UTF-8"?>
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?>
<zk>
<button label="Print AnnotateDataBinder">
	<attribute name="onClick">
		AnnotateDataBinder binder = page.getAttribute("binder");
		System.out.println("binder: " + binder);
	</attribute>
</button>

</zk>

link publish delete flag offensive edit

answered 2011-03-13 21:13:37 +0800

samchuang gravatar image samchuang
4084 4

3. TestAdminHandler's constructor is called twice ?

from your code, the TestAdminHandler is a composer and it's also a Initiator

I think
when ZK init use Initiator will create a instance first. Then when execute composer's doAfterCompose, it will create a instance again.

You can try to separate Initiator and Composer in different java class to test it.

link publish delete flag offensive edit

answered 2011-03-13 21:26:13 +0800

samchuang gravatar image samchuang
4084 4

4. AnnotateDataBinder will bind data in doAfterCompose.

so, you have many option to init data, for example, in Initiator's doInit, page init call before doAfterCompose

or, you can init data in doAfterCompose, and bind data again as you did in your code

you can also refer to Init_and_Cleanup

link publish delete flag offensive edit

answered 2011-03-30 04:49:21 +0800

jarnisb gravatar image jarnisb
36 1

Thanks for the help samchuang.

Using the same class as both initiator and composer quickly becomes both confusig and troublesome, as several seperate instances are created. I have modified my solution, and have done away with the need for another initiator besides AnnotateDataBinderInit. The code example posted below is not the exact same project (the first was example code simplified to show the problem, this code is the actual code I'm working on). I have made a few other modifications, like using spring, but I hope it will be helpful to someone else in a similar situation.
The zk jsp page:

<%@ page language="java" errorPage="/error.jsp" pageEncoding="UTF-8" contentType="text/html;charset=utf-8" %>
<%@ taglib prefix="zk" uri="http://www.zkoss.org/jsp/zul" %>
<link rel="stylesheet" type="text/css" href="<c:url value='/zkau/web/a5f28083/zul/css/zk.wcs'/>"/>

<zk:variable-resolver use="org.zkoss.zkplus.spring.DelegatingVariableResolver" />
<zk:init use="org.zkoss.zkplus.databind.AnnotateDataBinderInit" />

<zk:page>
    <zk:window id="zkWin" title="Administrer Kundeservicemedarbejdere" apply="${userAdminHandler}" border="normal" width="100%" height="100%">
        <zk:listbox id="userListBox" model="@{zkWin$composer.userListModel}" selectedItem="@{zkWin$composer.selected}" rows="25">
            <zk:auxhead>
                <zk:auxheader colspan="6" class="topic">Kundeservicemedarbejdere</zk:auxheader>
            </zk:auxhead>
            <zk:listhead>
                <zk:listheader label="Navn" sort="ascending" width="50%"/>
                <zk:listheader label="Windows login" sort="auto" width="10%"/>
                <zk:listheader label="Initialer" sort="auto" width="10%"/>
                <zk:listheader label="Titel" sort="auto" width="20%"/>
                <zk:listheader label="Admin" sort="auto" width="5%"/>
                <zk:listheader label="Chats" sort="auto" width="5%"/>
            </zk:listhead>
            <zk:listitem self="@{each=User}">
                <zk:listcell label="@{User.fullName}" />
                <zk:listcell label="@{User.username}" />
                <zk:listcell label="@{User.initials}" />
                <zk:listcell label="@{User.title}" />
                <zk:listcell label="@{User.admin}" />
                <zk:listcell label="@{User.noOfChatTimes}" />
            </zk:listitem>
        </zk:listbox>
        <zk:separator/>
        <zk:grid id="editUserGrid">
            <zk:auxhead>
                <zk:auxheader colspan="6" class="topic">Rediger</zk:auxheader>
            </zk:auxhead>
            <zk:columns>
                <zk:column label="Navn" align="center" width="50%" />
                <zk:column label="Windows login" align="center" width="10%" />
                <zk:column label="Initialer" align="center" width="10%" />
                <zk:column label="Titel" align="center" width="20%" />
                <zk:column label="Admin" align="center" width="5%"/>
                <zk:column label="Chats" align="center" width="5%"/>
            </zk:columns>
            <zk:rows>
                <zk:row>
                    <zk:textbox value="@{zkWin$composer.selected.fullName}" width="95%" />
                    <zk:textbox value="@{zkWin$composer.selected.username}" width="95%" />
                    <zk:textbox value="@{zkWin$composer.selected.initials}" width="95%" />
                    <zk:combobox id="comboDept" model="@{zkWin$composer.titleModel}" selectedItem="@{zkWin$composer.selected.title}" width="99%" />
                    <zk:checkbox checked="@{zkWin$composer.selected.admin save-when='self.onCheck'}" />
                    <zk:intbox value="@{zkWin$composer.selected.noOfChatTimes}" width="90%" />
                </zk:row>
            </zk:rows>
        </zk:grid>
    </zk:window>
</zk:page>

Notice the binding uses zkWin$composer.<composerField> notation to access fields in the composer object.

The composer class:

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.ForwardEvent;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zkplus.databind.AnnotateDataBinder;
import org.zkoss.zkplus.databind.BindingListModelList;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Window;

public class UserAdminHandler extends GenericForwardComposer {

    private Window zkWin;
    private AnnotateDataBinder binder;

    private UserService userService;
    private User selected;

    private List<User> userList;
    private BindingListModelList userListModel;

    private List<String> titleList;
    private ListModel titleModel;
    private Listbox userListBox;
    private Combobox comboboxTitle;

    public UserAdminHandler(){
        super();
    }

    @Override
    public void doAfterCompose(Component comp){
        try {
            super.doAfterCompose(comp);
        } catch (Exception e) {
            e.printStackTrace();
        }

        initModel();
    }

    private void initModel() {
        userList = userService.getSupportUsers();

        userListModel = new BindingListModelList(userList, true);
        selected = userList.get(0);

        titleList= Constants.SUPPORT_TITLES;
        titleModel = new BindingListModelList(titleList, true);
    }


    public void onAddUser(ForwardEvent event){
        User newUser = new User();
        userListModel.add(0, newUser);
        selected=newUser;
        userListBox.setSelectedIndex(0);
        binder.loadAll();
    }

    public void onDeleteUser(ForwardEvent event){
        userListModel.remove(selected);
        selected = null;
        binder.loadAll();        
    }

}

I have removed all not-zk-related imports and all getter/setter methods from the code for readability. Notice that the model is initialized/populated in the doAfterCompose method. At this point, AnnotationBinder (binder) has not yet been initialized, so the data binding cannot be setup programatically. Instead it is done later on by the AnnotationBinderInit if the @{binding} statements are correct. Notice the binder.loadAll(); call at the end of the methods that modify the selected field. While the BindingListModelList list seem to automatically update the listbox when an item is added or removed, the text fields bound to the selected user do not get updated if the binder is not specifically instructed to update them.

Finally, Spring's needs to have the composer bean added to it's application context for it to be injected by the window tag's apply="${userAdminHandler}

    <bean id="userAdminHandler" class="dk.telmore.app.webapp.action.admin.UserAdminHandler" scope="prototype" />

I hope someone finds this useful.

Jarnis Bertelsen
PS: ZK v. 5.0.5

link publish delete flag offensive edit

answered 2011-03-30 21:12:11 +0800

samchuang gravatar image samchuang
4084 4

@jarnisb

thanks for sharing your code

link publish delete flag offensive edit
Your reply
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!

[hide preview]

Question tools

Follow

RSS

Stats

Asked: 2011-03-07 09:06:55 +0800

Seen: 2,279 times

Last updated: Mar 30 '11

Support Options
  • Email Support
  • Training
  • Consulting
  • Outsourcing
Learn More