Richfaces 4 + Liferay: tree toggling issue with Ajax
christophe.noel Jan 22, 2013 4:36 AMDear all,
I have read all the stuff related to my issue: when switching to Ajax mode, I'm only able to toggle (expand) the first level nodes, but not the second level nodes.
I found the following related issue (but seem to be closed):
I just created the following issue ticket: RF-12743
Thanks so much to anyone who could help !
I'm using the following configuration:
- Liferay 6.0.5 on Tomcat v6.0
- JSF 2.1.3
- Richfaces 4.2.3 (but also tried with 4.2.2)
- Liferay Faces Bridge 3.1.0
Note : I first tried to implement my TreeNode by extending the richfaces TreeNodeImpl but, as I got a casting issue (cannot be cast to swing.tree, probably due to a classloading issue but I could find the cause), I finally implemented my own TreeNodeImpl implementing the swing.treenode class (then I got no more problem except the present issue).
The XHTML part (refine.xhtml). It displays a tree on 3 levels (1st level if an offeringNode, 2nd is a propertyNode, 3rd is a fieldNode) :
<rich:collapsiblePanel switchType="client" id="refinePanel" expanded="true"> <f:facet name="headerExpanded"> <h:graphicImage value="/images/icons/basket_edit.png" style="vertical-align:middle;"/> <h:outputText value=" Refinement"/> </f:facet> <f:facet name="headerCollapsed"> <h:graphicImage value="/images/icons/basket_edit.png" style="vertical-align:middle;"/> <h:outputText value=" Refinement"/> </f:facet> <b> <h:outputText value="Select Requested Fields"/></b> <rich:tree rendered="#{refineTreeBean.renderable}" id="tree" nodeType="#{node.type}" var="node" value="#{refineTreeBean.rootNodes}" toggleType="ajax"> <rich:treeNode id="offeringNode" type="offering" iconExpanded="/images/icons/folder.png" iconCollapsed="/images/icons/folder.png"> <h:selectBooleanCheckbox value="#{node.selected}"> <a4j:ajax event="change" render="refinePanel" action="#{refineTreeBean.refineAction()}"/> </h:selectBooleanCheckbox> #{node.name} </rich:treeNode> <rich:treeNode expanded="#{true}" id="propertyNode" type="property" iconExpanded="/images/icons/textfield_key.png" iconCollapsed="/images/icons/textfield_key.png"> <h:selectBooleanCheckbox value="#{node.selected}"> <a4j:ajax event="change" render="refinePanel" action="#{refineTreeBean.refineAction()}"/> </h:selectBooleanCheckbox> #{node.name} </rich:treeNode> <rich:treeNode id="fieldNode" type="field" iconLeaf="/images/icons/textfield.png"> <h:selectBooleanCheckbox value="#{node.selected}"> <a4j:ajax event="change" render="refinePanel" action="#{refineTreeBean.refineAction()}"/> </h:selectBooleanCheckbox> #{node.name} </rich:treeNode> </rich:tree> </rich:collapsiblePanel>
My RefineTreeBean (view scope). It includes a toggleNodeListener method, because I tried this way but another symptom is that I get an exception (IllegalArgument) on tree.getRowData() :
/** * */ package be.spacebel.ssa.swe.beans; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.tree.TreeNode; import org.richfaces.component.UITree; import org.richfaces.event.TreeSelectionChangeEvent; import org.richfaces.event.TreeToggleEvent; import be.spacebel.ssa.swe.data.FieldNode; import be.spacebel.ssa.swe.data.OfferingMetadata; import be.spacebel.ssa.swe.data.OfferingNode; import be.spacebel.ssa.swe.data.PropertyNode; import be.spacebel.ssa.swe.data.SelectableNode; /** * @author cnl * */ public class RefineTreeBean { private List<SelectableNode> rootNodes=new ArrayList<SelectableNode>(); private TreeNode currentSelection = null; private be.spacebel.ssa.swe.beans.MetadataBean metadataBean; private boolean renderable; public RefineTreeBean() { } public void toggleNodeListener(TreeToggleEvent ev){ UITree tree = (UITree)ev.getSource(); SelectableNode nodeExp = ((SelectableNode)tree.getRowData()); nodeExp.setExpanded(!nodeExp.isExpanded()); System.out.println("The node "+nodeExp.getName()+" has toggled!"); } public void refineAction() { System.out.println("checkbox clickeed"); } public void add(String clickedOffering, ArrayList<String> selectedProps) { this.renderable = true; OfferingMetadata desc = metadataBean.getOfferingDescriptions().get(clickedOffering); this.rootNodes.add(new OfferingNode(desc,selectedProps)); } public void remove(String clickedOffering) { for(SelectableNode node : this.rootNodes){ if(node.getName().equals(clickedOffering)) { this.rootNodes.remove(node); } } return; } public void addAll(ArrayList<String> offerings, ArrayList<String> selectedProps) { this.rootNodes = new ArrayList<SelectableNode>(); for(String off : offerings) { OfferingMetadata desc = metadataBean.getOfferingDescriptions().get(off); this.rootNodes.add(new OfferingNode(desc,selectedProps)); } this.renderable = true; //this.rootNodes.add(new OfferingNode(desc)); } public void removeAll() { this.renderable = false; this.rootNodes = new ArrayList<SelectableNode>(); return; } public void selectionChanged(TreeSelectionChangeEvent selectionChangeEvent) { // considering only single selection System.out.println("selection change"); /** List<Object> selection = new ArrayList<Object>(selectionChangeEvent.getNewSelection()); Object currentSelectionKey = selection.get(0); UITree tree = (UITree) selectionChangeEvent.getSource(); Object storedKey = tree.getRowKey(); tree.setRowKey(currentSelectionKey); currentSelection = (TreeNode) tree.getRowData(); tree.setRowKey(storedKey); * */ } public List<SelectableNode> getRootNodes() { return rootNodes; } public void setRootNodes(List<SelectableNode> rootNodes) { this.rootNodes = rootNodes; } public TreeNode getCurrentSelection() { return currentSelection; } public void setCurrentSelection(TreeNode currentSelection) { this.currentSelection = currentSelection; } public be.spacebel.ssa.swe.beans.MetadataBean getMetadataBean() { return metadataBean; } public void setMetadataBean( be.spacebel.ssa.swe.beans.MetadataBean metadataBean) { this.metadataBean = metadataBean; } public boolean isRenderable() { return renderable; } public void setRenderable(boolean renderable) { this.renderable = renderable; } }
An example here of what is an OfferingNode:
public class OfferingNode extends SelectableNode { public OfferingNode(OfferingMetadata desc, ArrayList<String> selectedProps) { super("offering", desc.getIdentifier()); this.selected=true; for(String property : desc.getObservablePropertyArray()) { if(selectedProps.size() == 0 || selectedProps.contains(property)) { addChild(property, new PropertyNode(property,desc,true)); } else { addChild(property, new PropertyNode(property,desc,false)); } } addChild(SWEUtil.UNQUALIFIED_FIELD, new PropertyNode(SWEUtil.UNQUALIFIED_FIELD,desc,false)); } }
This extends SelectableNode:
package be.spacebel.ssa.swe.data; import java.util.Collection; import javax.swing.tree.TreeNode; import be.spacebel.ssa.swe.util.TreeNodeImpl; /** * TreeNodeImpl from Richfaces cannot be used because of a class loading problem (probably) * But the issue couldn't be fixed (treenode cannot be cast to spring.treenode) * Therefore we created a TreeNodeImpl which implements TreeNode from spring * @author cnl * */ public class SelectableNode extends TreeNodeImpl { /** * Extension of a default TreeNode: this node has a name, and can be selectable * Type give indication on the node type (offering, property or field) */ protected String type; protected String name; protected boolean selected=false; protected boolean expanded=true; public SelectableNode(String type, String name) { super(); setType(type); setName(name); System.out.println("new Selectable Node"); } public boolean isExpanded() { return expanded; } public void setExpanded(boolean expanded) { this.expanded = expanded; } public boolean isSelected() { return selected; } public void setSelected(boolean selected) { System.out.println(getName()+ " is now "+selected); this.selected = selected; if(getChildCount()>0) { System.out.println("has children"); for(TreeNode node : this.getChildrenMap().values() ) { SelectableNode selectableNode = (SelectableNode) node; selectableNode.setSelected(selected); } } } public String getName() { return name; } public void setName(String name) { this.name = name; System.out.println(name); } public String getType() { return type; } public void setType(String type) { this.type = type; } @Override public String toString() { return this.name; } }
And my TreeNodeImpl:
public class TreeNodeImpl implements TreeNode { protected Map<Object, TreeNode> childrenMap; protected TreeNode parent; public TreeNodeImpl() { this.childrenMap = new HashMap<Object, TreeNode>(); } @Override public Enumeration<TreeNode> children() { return Collections.enumeration(this.childrenMap.values()); } @Override public boolean getAllowsChildren() { // TODO Auto-generated method stub return true; } @Override public TreeNode getChildAt(int arg0) { return this.childrenMap.get(arg0); } @Override public int getChildCount() { return this.childrenMap.size(); } @Override public int getIndex(TreeNode arg0) { return (new ArrayList<TreeNode>(this.childrenMap.values())).indexOf(arg0); } @Override public TreeNode getParent() { return this.parent; } public void setParent(TreeNode parent) { this.parent = parent; return; } public void addChild(Object identifier, TreeNodeImpl child) { this.childrenMap.put(identifier, child); child.setParent(this); } /** * remove public void addChildCond(Object identifier, TreeNodeImpl child) { if(!this.childrenMap.containsKey(identifier)) { addChild(identifier, child); } } */ public Map<Object, TreeNode> getChildrenMap() { return childrenMap; } public void setChildrenMap(Map<Object, TreeNode> children) { this.childrenMap = children; } @Override public boolean isLeaf() { // TODO Auto-generated method stub return (this.childrenMap.size() == 0); } }
The richfaces log is as follow when I try to expand the second level node :
debug[10:20:17.809]: New request added to queue. Queue requestGroupingId changed to A1568:uiSearchForm:tree:0.0:propertyNode debug[10:20:17.810]: Queue will wait 0ms before submit debug[10:20:17.814]: richfaces.queue: will submit request NOW info [10:20:17.816]: Received 'begin' event from <div id=A1568:uiSearchForm:tree:0.0:propertyNode class="rf-tr-nd" rf-tr-nd-colps ...> info [10:20:17.869]: Received 'beforedomupdate' event from <div id=A1568:uiSearchForm:tree:0.0:propertyNode class="rf-tr-nd" rf-tr-nd-colps ...> debug[10:20:17.870]: Server returned responseText: <partial-response><changes><update id="A1568:uiSearchForm:j_idt13"><![CDATA[<span id="A1568:uiSearchForm:j_idt13"> <div id="messageD"> <table id="messageT" class="rich-messages"> </table> </div></span>]]></update><update id="A1568:uiSearchForm:tree:0.0:propertyNode"><![CDATA[<div class="rf-tr-nd rf-tr-nd-colps" id="A1568:uiSearchForm:tree:0.0:propertyNode"><div class="rf-trn"><span class="rf-trn-hnd-colps rf-trn-hnd"></span><span class="rf-trn-cnt"><img class="rf-trn-ico-colps rf-trn-ico rf-trn-ico-cst" alt="" src="/LiferayTest-portlet/images/icons/textfield_key.png" /><span class="rf-trn-lbl"><input id="A1568:uiSearchForm:tree:0.0:j_idt87" type="checkbox" name="A1568:uiSearchForm:tree:0.0:j_idt87" checked="checked" onchange="RichFaces.ajax(this,event,{"parameters":{"javax.faces.behavior.event":"change","org.richfaces.ajax.component":"A1568:uiSearchForm:tree:0.0:j_idt87"} ,"sourceId":this} )" /> timeproperty </span></span></div></div>]]></update><eval><![CDATA[RichFaces.ui.TreeNode.initNodeByAjax("A1568:uiSearchForm:tree:0.0:propertyNode",{"clientEventHandlers":{} } )]]></eval><update id="javax.faces.ViewState"><![CDATA[7381444013844425114:-1903004591067256073]]></update></changes></partial-response> info [10:20:17.871]: Listing content of response changes element: Element update for id=A1568:uiSearchForm:j_idt13 <update id="A1568:uiSearchForm:j_idt13"><![CDATA[<span id="A1568:uiSearchForm:j_idt13"> <div id="messageD"> <table id="messageT" class="rich-messages"> </table> </div></span>]]></update> Element update for id=A1568:uiSearchForm:tree:0.0:propertyNode <update id="A1568:uiSearchForm:tree:0.0:propertyNode"><![CDATA[<div class="rf-tr-nd rf-tr-nd-colps" id="A1568:uiSearchForm:tree:0.0:propertyNode"><div class="rf-trn"><span class="rf-trn-hnd-colps rf-trn-hnd"></span><span class="rf-trn-cnt"><img class="rf-trn-ico-colps rf-trn-ico rf-trn-ico-cst" alt="" src="/LiferayTest-portlet/images/icons/textfield_key.png" /><span class="rf-trn-lbl"><input id="A1568:uiSearchForm:tree:0.0:j_idt87" type="checkbox" name="A1568:uiSearchForm:tree:0.0:j_idt87" checked="checked" onchange="RichFaces.ajax(this,event,{"parameters":{"javax.faces.behavior.event":"change","org.richfaces.ajax.component":"A1568:uiSearchForm:tree:0.0:j_idt87"} ,"sourceId":this} )" /> timeproperty </span></span></div></div>]]></update> Element eval <eval><![CDATA[RichFaces.ui.TreeNode.initNodeByAjax("A1568:uiSearchForm:tree:0.0:propertyNode",{"clientEventHandlers":{} } )]]></eval> Element update for id=javax.faces.ViewState <update id="javax.faces.ViewState"><![CDATA[7381444013844425114:-1903004591067256073]]></update> debug[10:20:17.873]: richfaces.queue: ajax submit successfull debug[10:20:17.873]: richfaces.queue: Nothing to submit info [10:20:17.873]: Received 'success' event from <div id=A1568:uiSearchForm:tree:0.0:propertyNode class="rf-tr-nd" rf-tr-nd-colps ...> info [10:20:17.873]: Received 'complete' event from <div id=A1568:uiSearchForm:tree:0.0:propertyNode class="rf-tr-nd" rf-tr-nd-colps ...>
Please find attached the java sources (but off course including a lot of other stuff). Thanks,
Christophe.
-
src.zip 38.4 KB