dinsdag 2 oktober 2007

jsf Solutions for many2many: the selectManyListBox

SelectManyListbox.



The selectManyListBox is an ordinary dropdown listbox with a list of selected values opposed to the single value for its SelectOneListbox sibbling. Unfortunately at the moment of implementing the ice:selectManyListbox was still buggy: the menurenderer.convertSelectValue didnot call the associated converter for multiple values. Apparently things have been ironed out in icefaces 1.6.1 Check the iceforum for a more detailed explanation.

<h2>Roles</h2>
<ice:selectManyListbox id="selectedRoles" value="#{subjectMgr.selectedRoles}" converter="#{subjectMgr.roleConverter}" size="5">
  <f:selectItems value="#{subjectMgr.allAvailableRoles}"/>
</ice:selectManyListbox>

You need 2 lists for selectmany listbox: a list with all available roles, individually stored into a selectitem and (2) a list with the selected roles. A converter (I think there is a seam tag, to circumvent the converter but at the moment of experimenting that was still in beta) to convert the values inside the selectitem tag to some unique serializable key (string) for rendering in the select tag and vice versa (in my case ordinary findById methods).
A successful display of the selectmanylistbox is not that hard, the hardest part is storing the selected and deleting the unselected roles.

Check the code below for SubjectMgrBean.setSelectedRoles. The implementation I came up with was a maintenance of three lists: the new list (originating from the selectManyListBox component), the list with actors to remove (A map with a Role - Actor mapping). And the list with actors to be created. The rolesToAdd and the rolesToRemove lists are initialized by the current roles on the subject in focus. For the removal we take the presentRoles (roles to Remove) exclude all roles in the newly selected roles, and we're are on our own with the roles which are up for deletion. An iteration is needed because in jpa-ql it is not possible to do a cascaded delete on a parent entity.
A similar operation is used for the roles to add. All roles appearing in the presentRoles, are removed from the selected roles leaving a collection with roles to add. Every Role is attached to the subject in focus, by creating a new Actor and adding it the set of existing actors. A final merge on the entity manager is sufficient to store all roles (actors) at once.
As you have noticed the code for the selectmanylistbox may not be the world's cleanest code. But I'm convinced that a lot of plumbing is needed to get this to work. The amount of code for such a simple use case is also contra intuitive Also major drawback is the big bug in icefaces forcing you to either hack their code or using the reference-jsf's selectmanylistbox implemention. Particular caveat is the implementation of the equals method of the entity beans. However the most annoying feature is the gui element automatically disselect all choices in the box when the user hits a wrong element without the cmd key pressed (on iMac), leaving nothing but the cancel button to the user. If i would have realized this feature earlier I probably would have never taken the selectmanylistbox for this use case.
PS the last part can be found in September.

SubjectMgrBean.java

001 package nl.jeroen.testdb.persist;
002 
003 import java.io.Serializable;
038 import org.jboss.seam.annotations.datamodel.DataModelSelection;
039 import ....;
041 import com.icesoft.util.DebugException;
042 
043 @Scope(ScopeType.CONVERSATION)
044 @Stateful
045 @Name("subjectMgr")
046 public class SubjectMgrBean implements SubjectMgr, Serializable {
047     @Logger
048     Log logger;
049         
050     public static final String EMPTY = "__EMPTY__";
051     private static final long serialVersionUID = 8523599219865145149L;
052     private Map<String, Role> roleValues;
053     private List<SelectItem> allAvailableRoles;
054     private List<Role> selectedRoles;
055     
056     @In(required=true)
057     IApplicationSession sessionstore;
058     
059     @PersistenceContext(type=PersistenceContextType.EXTENDED)
060     private EntityManager em;
061     
062     @In(required=false)
063     @Out(required=false)
064     private Subject subject;
065 
066     @DataModel(value="subjectList")
067     List<Subject> subjectList;
068     @DataModelSelection(value="subjectList")
069     @Out(required=false, value="focusSubject")
070     private Subject focusSubject;
071 
072     public List<SelectItem> getAllAvailableRoles() {
073         final String methodName = "getAllAvailableRoles: ";
074         if (logger.isTraceEnabled()) {
075             logger.trace(LogUtil.getLogMessage(methodName, LogUtil.VERSION,
076                     "starting '" "'"));
077         }
078         boolean populateRoleValues = false;
079 //        List<SelectItem> result = null;
080         if (allAvailableRoles == null || allAvailableRoles.size() == 0) {
081             List<Role> l = em.createQuery("from Role role").getResultList();
082             allAvailableRoles = new ArrayList<SelectItem>(l.size());
083             if (roleValues == null) {
084                 populateRoleValues = true;
085                 roleValues = new TreeMap<String, Role>();
086             }
087             for (Role r : l) {
088                 SelectItem item = new SelectItem(r,r.getName());
089                 allAvailableRoles.add(item);
090                 if (populateRoleValues) {
091                     roleValues.put(r.getName(),r);
092                 }
093             }
094             
095         }
096         if (logger.isDebugEnabled()) {
097             logger.debug(LogUtil.getLogMessage(methodName, LogUtil.VERSION,
098                 "ending with  '" + allAvailableRoles.size() "' in available roles"));
099         }
100         return allAvailableRoles;
101     }
102     public void setAllAvailableRoles(List<SelectItem> allAvailableRoles) {
103         this.allAvailableRoles = allAvailableRoles;
104     }
105     
106     public List<Role> getSelectedRoles() {
107         final String methodName = "getSelectedRoles: ";
108         if (logger.isTraceEnabled()) {
109             logger.trace(LogUtil.getLogMessage(methodName, LogUtil.VERSION,
110                     "starting '" + focusSubject.getSubjectid() "'"));
111         }
112         selectedRoles = em.createQuery("Select r from Role r, in (r.actors) a where a.subject = :subject")
113             .setParameter("subject", focusSubject).getResultList();
114         if (logger.isDebugEnabled()) {
115             logger.debug(LogUtil.getLogMessage(methodName, LogUtil.VERSION, "returning '" + selectedRoles.size() "'"));
116         }
117         return selectedRoles;
118     }
119     public void setSelectedRoles(List<Role> sltdRoles) {
120         final String methodName = "setSelectedRoles: ";
121         if (logger.isDebugEnabled()) {
122             logger.debug(LogUtil.getLogMessage(methodName,
123                     LogUtil.VERSION, "starting '" + sltdRoles.size() "'"));
124         }
125         try {
126             if (logger.isDebugEnabled()) {
127                 for (Role r : sltdRoles) {
128                     logger.debug(LogUtil.getLogMessage(methodName,
129                             LogUtil.VERSION, "selected roles: '" + r.getName() +  "' (" + r + ")"));
130                 }
131             }
132             Period prd = sessionstore.getFocusPeriod();
133             logger.info("focust period is #0 name is '#1'", prd.getPeriodid(), prd.getInfo());
134             Map<Role,Actor> presentRolesToRemove = new HashMap<Role, Actor>();
135             Set<Role> presentRolesToCreate = new HashSet<Role>();
136             for (Actor act :focusSubject.getActors()) {
137                 presentRolesToRemove.put(act.getRole(), act);
138                 presentRolesToCreate.add(act.getRole());
139             }
140             presentRolesToRemove.keySet().removeAll(sltdRoles);
141             for (Role r : presentRolesToRemove.keySet()) {
142                 Actor act = presentRolesToRemove.get(r);
143                 focusSubject.getActors().remove(act);
144                 em.remove(act);
145             }
146             sltdRoles.removeAll(presentRolesToCreate);
147             for (Role r : sltdRoles) {
148                 focusSubject.getActors().add(new Actor(-1,sessionstore.getFocusPeriod(), r, focusSubject));
149             }
150             em.merge(focusSubject);
151         catch (Throwable e) {
152             logger.error("error ocurrued", e);
153         }
154     }
155     
156     public Converter getRoleConverter() {
157         return new Converter (){
158             
159             public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2throws ConverterException {
160                 final String methodName = "getAsObject";
161                 if (logger.isTraceEnabled()) {
162                     logger.trace(LogUtil.getLogMessage(LogUtil.VERSION, methodName
163                         "start with argument '" + arg2 + "'"));
164                 }
165                 if (arg2 == null || arg2.equals(""|| arg2.equals(EMPTY)) {
166                     return null;
167                 }
168                 try {
169                     int roleid = Integer.parseInt(arg2);
170                     Iterator<SelectItem> iter = allAvailableRoles.iterator();
171                     boolean found = false;
172                     Role result = null;
173                     while (!found && iter.hasNext()) {
174                         result = (Roleiter.next().getValue();
175                         if (result.getRoleid() == roleid
176                             found = true;
177                     }
178                     try {
179                         EntityManager em1 = (EntityManagerComponent.getInstance("entityManager"true);
180                         em1.find(Role.class, roleid);
181                     catch (Exception e) {
182                         logger.error(LogUtil.getLogMessage(methodName,
183                                 LogUtil.VERSION, "Error occured retrieving them from inner class."), e);
184                     }
185                     if (logger.isDebugEnabled()) {
186                         logger.debug(LogUtil.getLogMessage(methodName,
187                                 LogUtil.VERSION, "'" + roleid + "'"));
188                     }
189                     return result;
190                 catch (NumberFormatException nfe) {
191                     logger.error(LogUtil.getLogMessage(LogUtil.VERSION, methodName 
192                         "passed argument '" + arg2 + "' cannot converted to int"), nfe);
193                 }
194                 return null;
195             }
196     
197             public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2throws ConverterException {
198                 final String methodName = "getAsString";
199                 if (logger.isTraceEnabled()) {
200                     logger.trace(LogUtil.getLogMessage(LogUtil.VERSION, methodName
201                         "start '" + arg2 + "' of type '" (arg2 != null ? arg2.getClass() """'"));
202                 }
203                 if (arg2 == nullreturn EMPTY;
204                 if ("-1".equals(arg2)) return EMPTY;
205                 if (arg2 instanceof Role) {
206                     return "" ((Role)arg2).getRoleid();
207                 else {
208                     logger.error(LogUtil.getLogMessage(LogUtil.VERSION, methodName 
209                         "argument is not expected type Role returning _EMPTY"));
210                 }
211                 return EMPTY;
212             }
213         };
214         
215     }
216 
217 
218     public Role findRoleById(Integer id) {
219         return (Roleem.createQuery("r from Role r where r.roleid = :id").setParameter("id", id).getSingleResult();
220     }
221 

Geen opmerkingen: