-
FEATURED COMPONENTS
First time here? Check out the FAQ!
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); } }
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
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
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
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>
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.
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
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>
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(); } }
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
Asked: 2011-03-07 09:06:55 +0800
Seen: 2,279 times
Last updated: Mar 30 '11