ZK - Open Source Ajax Java FrameworkZK - Open Source Ajax Java Framework

Get Multiselcted items from listbox and annotation

fusion35
16 Apr 2009 12:11:37 GMT
16 Apr 2009 12:11:37 GMT

Hi :

zul snippet :

<listbox id="lbCategory" width="350px" multiple="true" checkmark="true" rows="4" selectedItems="@{applicant.applicantGroup}"/>

The model :


private transient Set<MasterApplicantGroup> applicantGroup;

public Set<MasterApplicantGroup> getApplicantGroup() {
   return applicantGroup;
}

public void setApplicantGroup(Set<MasterApplicantGroup> applicantGroup) {
    this.applicantGroup = applicantGroup;
}


In my controller when I do binder.loadAll() it gives me a Nullpointer exception. If applicantGroup is initalized then the null pointer goes away but binder.saveAll() doesn't have the selectedItems in it.

What wrong am I doing?

regards

Devinder

iantsai
17 Apr 2009 01:29:36 GMT
17 Apr 2009 01:29:36 GMT

Because your listbox didn't contain anything.

And I'm not sure that Annotated Databinding support multiple selection in listbox.

If your requirement need to do a lot of detail control of Listbox, then using Model + renderer would be safety.

Sometimes it's better to choose the folksy approach.

fusion35
17 Apr 2009 03:43:41 GMT
17 Apr 2009 03:43:41 GMT

Thanks for the reply.

>>Because your listbox didn't contain anything.

I have it, both model and renderer are set in the controller.

    public void onCreate$applicantWindow() {
        try {
            lbCategory.setModel(new ListModelList(ApiManager.getMasterGroupDao().readAllMasterGroup()));
            lbCategory.setItemRenderer(new SimpleListboxRenderer());

            applicant = (MasterApplicant) params.get("applicant");

            binder = new AnnotateDataBinder(this);
            binder.bindBean("applicant", applicant);
            binder.loadAll();

        } catch (DAOException ex) {
            log.error("onCreate$applicantWindow : "+ex.getMessage());
        }
    }

To make it work I have taken away the selectedItems="@{applicant.applicantGroup}" from the list box and set the bean value manually (using getSelecteItems()). My question remains how do I get the values using annotation?

regards

Devinder

iantsai
20 Apr 2009 01:09:26 GMT
20 Apr 2009 01:09:26 GMT

Ok, now I see your problem.

Instead of ListModelList, maybe you should try this one:
org.zkoss.zkplus.databind.BindingListModelList

fusion35
20 Apr 2009 14:09:27 GMT
20 Apr 2009 14:09:27 GMT

Thanks for the reply.

That doesn't work either.

regards

Devinder

iantsai
21 Apr 2009 01:38:48 GMT
21 Apr 2009 01:38:48 GMT

Are you sure "applicant" is a instance from ApiManager.getMasterGroupDao().readAllMasterGroup()?

If the instances are different then there's nothing Binder can do.

fusion35
21 Apr 2009 10:36:20 GMT
21 Apr 2009 10:36:20 GMT

Thanks for the reply.

No. "applicant" is an instance of "MasterApplicant"

 applicant = (MasterApplicant) params.get("applicant");

In MasterApplicant (my model) I have a class variable :

 private transient Set<MasterApplicantGroup> applicantGroup;

to which I am referring in zul file as :
<listbox id="lbCategory" width="350px" multiple="true" checkmark="true" rows="4" selectedItems="@{applicant.applicantGroup}"/>

Is there something that I am missing?

regards

Devinder

iantsai
22 Apr 2009 01:58:15 GMT
22 Apr 2009 01:58:15 GMT

Finally I have checked the Javadoc, and now I sorry say: "you can't make it in this approach."
because there's no "setSelectedItems(Set selected)" method in listbox!


So now the way to do your job is control it all by your self. In this complex situation I think we should forget Annotated Databinding.
This technology doesn't feet this situation.


    public void onCreate$applicantWindow() {
        try {
            ListModelList groupModel = new ListModelList(ApiManager.getMasterGroupDao().readAllMasterGroup());
            lbCategory.setModel(groupModel );
            lbCategory.setItemRenderer(new SimpleListboxRenderer());
            applicant = (MasterApplicant) params.get("applicant");
            Listitem item;
            for(MasterApplicantGroup group : applicant.getApplicantGroup()){
                item = lbCategory.getItemAtIndex(groupModel.indexOf(group))
                lbCategory.addItemToSelection(item);
            }
            
            
        } catch (DAOException ex) {
            log.error("onCreate$applicantWindow : "+ex.getMessage());
        }
    }


robertpic71Top Contributor
22 Apr 2009 03:22:49 GMT
22 Apr 2009 03:22:49 GMT

Multiple databinding with annotations is only available with an own converter.
The user dasultz has written one.

<listbox id="lbCategory" width="350px" multiple="true" checkmark="true" rows="4" selectedItems="@{applicant.applicantGroup, converter=daslutz.magiccode}"/>

Check this thread.

Post if you have any troubles - i do not need multiple selection until now... so i have not testet the code. But user dasultz seems to be a poweruser, so i think the code shoudl work.

Or use the non-databinding-way as descriped by Iantsai.

/Robert

fusion35
22 Apr 2009 04:38:12 GMT
22 Apr 2009 04:38:12 GMT

Thanks guys for the reply.

Iantsai, there is a setSelectedItems method in listbox which accepts set (since 3.6.0, a new addition I guess)

public void setSelectedItems(java.util.Set listItems)

which is why my model variable is a set.

private transient Set<MasterApplicantGroup> applicantGroup;

I was thinking a mere selectedItems="@{applicant.applicantGroup} should work. I will also to look into the Dasulz's approach of converter, as suggested by Robert.

I am already using manual approach as you too suggested, but would definitely like to see the annotation working.

regards

Devinder

shumy
29 Apr 2009 17:52:51 GMT
29 Apr 2009 17:52:51 GMT

Solution provided by robertpic71 won't work because the listbox doesn't update the selectedItems wen items are selected.
And the converter fails sometimes wen listbox has listhead's or other different child components.
Also,

public void setSelectedItems(java.util.Set listItems)
is not in the org.zkoss.zul.api.Listbox interface.

fusion35
30 Apr 2009 10:35:39 GMT
30 Apr 2009 10:35:39 GMTThanks for the reply Shumy. I may not be entirely clear here but does that make any difference not to have a method in the interface but in the class? regards Devinder
shumy
30 Apr 2009 14:43:17 GMT
30 Apr 2009 14:43:17 GMT

Yes, because you can change the Component implementation providing the "use" property and access the component by interface in your composer class. This is the correct way, by decoupling interface and implementation.
And I can't access method setSelectedItems with the interface, and this is clearly an interface method.

fusion35
30 Apr 2009 15:29:06 GMT
30 Apr 2009 15:29:06 GMTThanks for the reply. Any reason you could see why it is not added in the interface but in the class? regards Devinder
iantsai
4 May 2009 01:32:21 GMT
4 May 2009 01:32:21 GMT

I have post it to SF.net as a feature request, I think we'll get the answer soon.
https://sourceforge.net/tracker/?func=detail&aid=2786357&group_id=152762&atid=785194

robertpic71Top Contributor
4 May 2009 23:03:57 GMT
4 May 2009 23:03:57 GMT

Yes, i think the selectedItems are "unfinished". Databinding is working only one-way. My TypeConverter is not called for CorcetoBean.

I extend the original SelectedItemConverter, here is my work-a-round:
Because the selectedItems is only working in one-way, i use selectedItem and update the listbox(entries) inside the converter.

Tested with 3.6.0, 3.6.1 and listbox with livemodel - should also work for listbox w/o livemodel.

It works, but is a little bit tricky:
1. because it's the single selectItem it is called for every selected Listitem (only extra work, no problems)
2. I do not (re)fire an onSelect-Event for every (selected) Listitem (i think there is no need with databinding)

3. When the method selectItems is ready for databinding, i'll post my converter for this also.

/Robert

<listbox model="@{model.users}" multiple="true" rows="5" width="400px"
		    selectedItem="@{model.ticket.users, converter=webtests.binder.SelectedItemsConverterV3}">
	<listhead>
		<listheader label="Id"/>
		<listheader label="Name"/>
	</listhead>
	<listitem self="@{each=user}">
		<listcell label="@{user.id}" />
		<listcell label="@{user.name}" />
	</listitem>
</listbox>

robertpic71Top Contributor
4 May 2009 23:51:47 GMT
4 May 2009 23:51:47 GMT

And the javacode:

package webtests.binder;


import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.SelectEvent;
import org.zkoss.zkplus.databind.BindingListModel;
import org.zkoss.zkplus.databind.TypeConverter;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listitem;

/**
 * Convert selected items to bean and vice versa.
 *
 */
public class SelectedItemsConverterV3 implements TypeConverter, java.io.Serializable {
	
	private static final long serialVersionUID = 1L;


	public Object coerceToUi(Object val, Component comp) { //load
		Listbox lbx = (Listbox) comp;
		if (val != null & val instanceof Collection) {
			Collection<Object> values = (Collection) val;
			Iterator<Object> valuesIterator = values.iterator();
			Set<Object> items = new HashSet<Object>();

			final ListModel xmodel = lbx.getModel();
			if (xmodel instanceof BindingListModel) {
				final BindingListModel model = (BindingListModel) xmodel;
				Listitem item = null;
				while (valuesIterator.hasNext()) {
					Object value = valuesIterator.next();
					int index = model.indexOf(value);
					if (index >= 0) {
						item = (Listitem) lbx.getItemAtIndex(index);
						if (item != null) {
							items.add(item);
						}
					}
				}	
				//We need this to support load-when:onSelect when first load 
				//the page in (so it is called only once).
				if (items.size() > 0 & items.size() != lbx.getSelectedCount()) { // bug 1647817, avoid endless-loop
					//bug #2140491
					Executions.getCurrent().setAttribute("zkoss.zkplus.databind.ON_SELECT"+lbx.getUuid(), Boolean.TRUE);
					Events.postEvent(new SelectEvent("onSelect", lbx, items, item));
				}    
				lbx.setSelectedItems(items);
				return TypeConverter.IGNORE;

			} else if (xmodel == null) { //no model case, assume Listitem.value to be used with selectedItem
				//iterate to find the selected item assume the value (select mold)
				while (valuesIterator.hasNext()) {
					Object value = valuesIterator.next();
					for (Iterator it = lbx.getItems().iterator(); it.hasNext();) {
						Listitem item = (Listitem) it.next();
						if (value.equals(item.getValue())) {
							items.add(item);
						}
					}
				}
				lbx.setSelectedItems(items);
				return TypeConverter.IGNORE;
			} else {
				throw new UiException("model of the databind listbox "+lbx+" must be an instanceof of org.zkoss.zkplus.databind.BindingListModel." + xmodel);
			}
		}
	  	return null;
	}
  
	public Object coerceToBean(Object val, Component comp) { //save
	  	final Listbox lbx = (Listbox) comp;
		if (Executions.getCurrent().getAttribute("zkoss.zkplus.databind.ON_SELECT"+lbx.getUuid()) != null) {
			//bug #2140491
			//triggered by coerceToUi(), ignore this
			Executions.getCurrent().removeAttribute("zkoss.zkplus.databind.ON_SELECT"+lbx.getUuid());
			return TypeConverter.IGNORE;
		}
	  	if (val != null) {
	  		final ListModel model = lbx.getModel();
	  		Set<Listitem> listitems = (Set<Listitem>) lbx.getSelectedItems();
	  		Iterator<Listitem> itemsIterator = listitems.iterator();
	  		HashSet<Object> selectedValues = new HashSet<Object>();
	  		
	  		while (itemsIterator.hasNext()) {
				Listitem listitem = itemsIterator.next();
				if (model != null) { // model case
					selectedValues.add(model.getElementAt(listitem.getIndex()));
				} else { // no model case --> Value
					selectedValues.add(listitem.getValue());
				}
	  		}	
	  		return selectedValues;
	  	}
	 	return null;
	}
}

fusion35
5 May 2009 02:36:14 GMT
5 May 2009 02:36:14 GMTThanks for the code, Robert I will give it a try. regards Devinder
rouggio
8 May 2009 08:33:18 GMT
8 May 2009 08:33:18 GMT

Robertpic71 - thanks! it works for me :)

I just changed this line:

> if (val != null & val instanceof Collection) {

to use the and comparison logical operator "&&"

> if (val != null && val instanceof Collection) {

...and added a couple @SuppressWarning("unchecked") lines to have a clean compile log

robertpic71Top Contributor
8 May 2009 13:45:16 GMT
8 May 2009 13:45:16 GMT

>> & -> &&
Thanks, I correct for future posts (when set/getSelectedItems is ready for databinding).

>> it works for me :)
Yes for my example also. It is nice no handle n:m relations without any extra javacode.
Just databinding (with this converter) and getters for the servicelayer/DAO's.

/Robert

1 2