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