/**
* License Agreement.
*
* JBoss RichFaces - Ajax4jsf Component Library
*
* Copyright (C) 2007 Exadel, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.richfaces.renderkit;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.faces.FacesException;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.el.MethodBinding;
import org.ajax4jsf.javascript.JSFunction;
import org.ajax4jsf.javascript.JSReference;
import org.ajax4jsf.javascript.ScriptUtils;
import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.renderkit.AjaxRendererUtils;
import org.ajax4jsf.renderkit.ComponentsVariableResolver;
import org.richfaces.component.UITree;
import org.richfaces.component.UITreeNode;
import org.richfaces.component.nsutils.NSUtils;
import org.richfaces.component.state.TreeState;
import org.richfaces.component.state.TreeStateAdvisor;
import org.richfaces.model.LastElementAware;
import org.richfaces.model.TreeRange;
import org.richfaces.model.TreeRowKey;
public abstract class TreeRendererBase extends CompositeRenderer {
protected final static Comparator treeRowKeyComparator = new Comparator() {
public int compare(Object key1, Object key2) {
TreeRowKey treeRowKey1 = (TreeRowKey) key1;
TreeRowKey treeRowKey2 = (TreeRowKey) key2;
if (treeRowKey1 == null) {
if (treeRowKey2 == null) {
return 0;
} else {
return -1;
}
} else {
if (treeRowKey2 == null) {
return 1;
}
}
Iterator iterator1 = treeRowKey1.iterator();
Iterator iterator2 = treeRowKey2.iterator();
while (iterator1.hasNext() && iterator2.hasNext()) {
String id1 = iterator1.next().toString();
String id2 = iterator2.next().toString();
int cr = id1.compareTo(id2);
if (cr != 0) {
return cr;
}
}
if (iterator1.hasNext()) {
return 1;
} else if (iterator2.hasNext()) {
return -1;
} else {
return 0;
}
}
};
private final class RendererDataModelEventNavigator extends
TreeDataModelEventNavigator {
private final FacesContext context;
private final UITree tree;
private final Flag droppedDownToLevelFlag;
private final ResponseWriter writer;
private String clientId;
private boolean expanded;
private boolean showLines;
private RendererDataModelEventNavigator(UITree tree,
TreeRowKey floatingKey, FacesContext context, Flag droppedDownToLevelFlag) {
super(tree, floatingKey);
this.context = context;
this.tree = tree;
this.droppedDownToLevelFlag = droppedDownToLevelFlag;
this.writer = context.getResponseWriter();
this.expanded = this.tree.isExpanded();
this.showLines = this.tree.isShowConnectingLines();
this.clientId = getClientId();
}
public void followRowKey(FacesContext context, TreeRowKey newRowKey) throws IOException {
super.followRowKey(context, newRowKey);
this.expanded = this.tree.isExpanded();
this.clientId = getClientId();
}
private String getClientId() {
Object rowKey = tree.getRowKey();
String id;
if (rowKey == null) {
id = tree.getClientId(context)
+ NamingContainer.SEPARATOR_CHAR;
} else {
id = tree.getNodeFacet().getClientId(context)
+ NamingContainer.SEPARATOR_CHAR;
}
return id;
}
public void afterUp(int levels) throws IOException {
Context c = droppedDownToLevelFlag.getContext();
if (c != null) {
c.setHasChildren(false);
openDiv(c);
closeDiv();
droppedDownToLevelFlag.setContext(null);
}
//writer.write("** afterUp **");
for (int i = 0; i < levels; i++) {
closeDiv();
}
//if (!isLastElement) closeDiv();
}
public void afterDown() throws IOException {
}
public void beforeDown() throws IOException {
Context c = droppedDownToLevelFlag.getContext();
droppedDownToLevelFlag.setContext(null);
openDiv(c);
//writer.write("** beforeDown **");
//if (this.getRowKey()==null ) openDiv();
}
public void beforeUp(int levels) throws IOException {
}
public void openDiv(Context context) throws IOException {
writer.startElement("div", tree);
if (context == null) {
context = new Context();
context.setLast(this.actualLast);
context.setClientId(this.clientId);
context.setExpanded(this.expanded);
context.setRowKey(this.getRowKey());
}
getUtils().writeAttribute(writer, "id", context.getClientId() + "childs");
if (!context.isExpanded() || !context.isHasChildren()) {
getUtils().writeAttribute(writer, "style", "display: none;");
} else {
if (tree.isShowConnectingLines()) {
TreeRowKey floatingKey = getFloatingKey();
//need the expression only for AJAX update root
if (floatingKey != null && floatingKey.equals(context.getRowKey())) {
String expression = "background-image:expression(this.nextSibling ? '' : 'none')";
getUtils().writeAttribute(writer, "style", expression);
}
}
}
String styleClasses = "";
if (context.getRowKey() != null) {
styleClasses = "dr-tree-layout-on dr-tree-h-ic-div rich-tree-node-children rich-tree-node-cildren";
if (!context.isLast() && showLines) styleClasses += " dr-tree-h-ic-line";
}
if (styleClasses!="") getUtils().writeAttribute(writer, "class", styleClasses);
}
public void closeDiv() throws IOException {
writer.endElement("div");
}
}
private class DataVisitorWithLastElement implements DataVisitor,
LastElementAware {
private boolean isLastElement = false;
private final Flag flag;
private final UITree tree;
private final RendererDataModelEventNavigator navigator;
private TreeStateAdvisor methodBindingAdvisor = null;
private Object floatingKey;
private DataVisitorWithLastElement(Flag flag, UITree tree,
RendererDataModelEventNavigator navigator, Object rowKey) {
this.flag = flag;
this.tree = tree;
this.navigator = navigator;
this.floatingKey = rowKey;
}
public void process(FacesContext context, Object rowKey, Object argument)
throws IOException {
TreeRowKey<?> treeRowKey = (TreeRowKey<?>) rowKey;
processAdvisors(context, treeRowKey);
navigator.followRowKey(context, treeRowKey);
Context c = flag.getContext();
if (c != null) {
c.setHasChildren(false);
navigator.openDiv(c);
navigator.closeDiv();
}
UITreeNode nodeFacet = tree.getNodeFacet();
Object oldAttrValue = nodeFacet.getAttributes().get("isLastElement");
Object oldAjaxRootAttrValue = nodeFacet.getAttributes().get("isAjaxUpdateRoot");
try {
nodeFacet.getAttributes().put("isLastElement", new Boolean(isLastElement));
nodeFacet.getAttributes().put("isAjaxUpdateRoot", new Boolean(floatingKey != null && floatingKey.equals(rowKey)));
ResponseWriter writer = context.getResponseWriter();
if (isLastElement && this.navigator.showLines) {
writer.startElement("p", tree);
writer.writeAttribute("class", "dr-tree-last-node-marker", null);
writer.endElement("p");
}
renderChild(context, nodeFacet);
c = new Context();
c.setClientId(nodeFacet.getClientId(context) + NamingContainer.SEPARATOR_CHAR);
c.setLast(this.isLastElement);
c.setExpanded(tree.isExpanded());
c.setRowKey(tree.getRowKey());
flag.setContext(c);
//writer.write("** after renderChild **");
//navigator.openDiv();
} finally {
if (oldAttrValue != null) {
nodeFacet.getAttributes().put("isLastElement", oldAttrValue);
} else {
nodeFacet.getAttributes().remove("isLastElement");
}
if (oldAjaxRootAttrValue != null) {
nodeFacet.getAttributes().put("isAjaxUpdateRoot", oldAjaxRootAttrValue);
} else {
nodeFacet.getAttributes().remove("isAjaxUpdateRoot");
}
}
}
public void setLastElement() {
isLastElement = true;
navigator.setLastElement();
}
public void resetLastElement() {
isLastElement = false;
navigator.resetLastElement();
}
public void processAdvisors(FacesContext context, TreeRowKey rowKey) throws IOException {
TreeState state = (TreeState) tree.getComponentState();
TreeStateAdvisor stateAdvisor = (TreeStateAdvisor)tree.getStateAdvisor();
if (null == stateAdvisor) {
if (null == methodBindingAdvisor) {
methodBindingAdvisor = new TreeStateAdvisor() {
public Boolean adviseNodeOpened(UITree tree) {
MethodBinding adviseNodeOpened = tree.getAdviseNodeOpened();
if (null != adviseNodeOpened) {
return (Boolean) adviseNodeOpened.invoke(FacesContext.getCurrentInstance(), new Object[] {tree});
}
return null;
}
public Boolean adviseNodeSelected(UITree tree) {
MethodBinding adviseNodeSelected = tree.getAdviseNodeSelected();
if (null != adviseNodeSelected) {
return (Boolean) adviseNodeSelected.invoke(FacesContext.getCurrentInstance(), new Object [] {tree});
}
return null;
}
};
}
stateAdvisor = methodBindingAdvisor;
}
Boolean adviseOpened = stateAdvisor.adviseNodeOpened(tree);
if (null != adviseOpened) {
if (adviseOpened.booleanValue()) {
state.makeExpanded(rowKey);
} else {
state.makeCollapsed(rowKey);
}
}
Boolean adviseSelected = stateAdvisor.adviseNodeSelected(tree);
if (null != adviseSelected) {
if (adviseSelected.booleanValue()) {
if (!state.isSelected(rowKey)) {
state.setSelected(rowKey);
}
}
else {
if (state.isSelected(rowKey)) {
state.setSelected(null);
}
}
}
}
}
public TreeRendererBase() {
super();
addContributor(DraggableRendererContributor.getInstance());
addContributor(DropzoneRendererContributor.getInstance());
addParameterEncoder(DnDParametersEncoder.getInstance());
}
public void writeNamespace(FacesContext context, UIComponent component) throws IOException {
NSUtils.writeNameSpace(context, component);
}
public void encodeAjaxChildren(FacesContext context, UIComponent component,
String path, Set ids, Set renderedAreas) throws IOException {
super.encodeAjaxChildren(context, component, path, ids, renderedAreas);
try {
if (component instanceof UITree) {
UITree tree = (UITree) component;
String id = path + tree.getId();
tree.captureOrigValue();
//Object rowKey = tree.getRowKey();
boolean encodeScripts = false;
tree.setRowKey(context, null);
//we should add xmlns to AJAX response
//we'll write neutral inner element and add xmlns there
ResponseWriter responseWriter = context.getResponseWriter();
responseWriter.startElement("div", tree);
writeNamespace(context, component);
List encodedAreaIds = new ArrayList();
try {
Set ajaxKeys = tree.getAllAjaxKeys();
if (ajaxKeys != null) {
List sortedKeys = new ArrayList(ajaxKeys.size());
sortedKeys.addAll(ajaxKeys);
Collections.sort(sortedKeys, treeRowKeyComparator);
Iterator ajaxKeysItr = sortedKeys.iterator();
TreeRowKey lastKey = null;
boolean nullRoot = false;
while (!nullRoot && ajaxKeysItr.hasNext()) {
TreeRowKey key = (TreeRowKey) ajaxKeysItr.next();
if (lastKey == null) {
lastKey = key;
} else {
if (!lastKey.isSubKey(key)) {
lastKey = key;
} else {
//skip nodes that's parent nodes have been rendered
continue;
}
}
if (key == null || key.depth() == 0) {
nullRoot = true;
key = null;
}
tree.setRowKey(context, key);
if (key == null || tree.isRowAvailable()) {
String treeClientId;
if (key == null) {
treeClientId = tree.getClientId(context);
} else {
treeClientId = tree.getNodeFacet().getClientId(context);
}
String treeChildrenId = treeClientId + NamingContainer.SEPARATOR_CHAR + "childs";
writeContent(context, tree, key);
encodeScripts = true;
renderedAreas.add(treeClientId);
encodedAreaIds.add(treeClientId);
renderedAreas.add(treeChildrenId);
//encodedAreaIds.add(id+":childs");
} else {
String cid = tree.getClientId(context);
String message = MessageFormat.format(
"Failed to re-render tree node: {0} due to model data unavailability! " +
"Maybe parent node should be re-rendered instead?",
new Object[] { cid });
ExternalContext externalContext = context.getExternalContext();
externalContext.log(message);
}
}
//ajaxKeys.clear();
}
} catch (Exception e) {
throw new FacesException(e);
} finally {
try {
tree.setRowKey(context, null);
tree.restoreOrigValue();
} catch (Exception e) {
context.getExternalContext().log(e.getMessage(), e);
}
}
if (encodeScripts) {
writeScript(context, tree, encodedAreaIds, renderedAreas);
String inputId = encodeSelectionStateInput(context, tree);
if (inputId != null) {
renderedAreas.add(inputId);
}
}
responseWriter.endElement("div");
tree.clearRequestKeysSet();
}
} finally {
try {
ComponentsVariableResolver.removeVariables(this, component);
} catch (Exception e) {
context.getExternalContext().log(e.getMessage(), e);
}
}
}
public String encodeSelectionStateInput(FacesContext context, UITree tree) throws IOException {
String result = "";
TreeState treeState = (TreeState) tree.getComponentState();
TreeRowKey selectedNodeKey = treeState.getSelectedNode();
if (selectedNodeKey != null) {
Object rowKey = tree.getRowKey();
try {
tree.setRowKey(selectedNodeKey);
if (tree.isRowAvailable()) {
result = tree.getNodeFacet().getClientId(context);
}
} finally {
try {
tree.setRowKey(rowKey);
} catch (Exception e) {
context.getExternalContext().log(e.getMessage(), e);
}
}
}
ResponseWriter writer = context.getResponseWriter();
writer.startElement("input", tree);
writer.writeAttribute("type", "hidden", null);
String selectionHolderInputId = tree.getSelectionStateInputName(context);
writer.writeAttribute("id", selectionHolderInputId, null);
writer.writeAttribute("name", selectionHolderInputId, null);
writer.writeAttribute("value", result, null);
writer.endElement("input");
return selectionHolderInputId;
}
protected String getAjaxScript(FacesContext context, UITree tree) {
String id = tree.getBaseClientId(context);
JSFunction function = AjaxRendererUtils
.buildAjaxFunction(tree, context);
Map eventOptions = AjaxRendererUtils.buildEventOptions(context, tree);
Map parameters = (Map) eventOptions.get("parameters");
parameters.remove(id);
parameters.put(id + UITree.SELECTED_NODE_PARAMETER_NAME,
new JSReference("event.selectedNode"));
function.addParameter(eventOptions);
StringBuffer buffer = new StringBuffer();
function.appendScript(buffer);
buffer.append("; return false;");
return buffer.toString();
}
protected String getScriptContributions(FacesContext context, UITree tree) {
return super.getScriptContributions(getJavaScriptVarName(context, tree), context, tree);
}
protected String getJavaScriptVarName(FacesContext context, UITree tree) {
String id = tree.getBaseClientId(context);
return "Richfaces_Tree_" + id.replaceAll("[^A-Za-z0-9_]", "_");
}
private void writeScript(FacesContext context, UITree tree, List encodedAreaIds,
Set renderedAreas) throws IOException {
final ResponseWriter writer = context.getResponseWriter();
final String clientId = tree.getBaseClientId(context);
String scriptId = clientId + NamingContainer.SEPARATOR_CHAR + "script";
writer.startElement("div", tree);
getUtils().writeAttribute(writer, "id", scriptId);
writer.startElement("script", tree);
getUtils().writeAttribute(writer, "type", "text/javascript");
String varName = getJavaScriptVarName(context, tree);
writer.writeText(varName + ".getNodeElements(" +
ScriptUtils.toScript(encodedAreaIds) + ");", null);
writer.endElement("script");
writer.endElement("div");
renderedAreas.add(tree.getClientId(context)
+ NamingContainer.SEPARATOR_CHAR + "input");
renderedAreas.add(scriptId);
}
public void encodeChildren(FacesContext context, UIComponent component)
throws IOException {
writeContent(context, (UITree) component, null);
}
public void writeContent(final FacesContext context, final UITree input)
throws IOException {
writeContent(context, input, null);
}
public void writeContent(final FacesContext context, final UITree input,
TreeRowKey key) throws IOException {
// simple flag can be used here because
// we cannot jump more than one level down until next node
// when rendering
Flag droppedDownToLevelFlag = new Flag();
TreeRowKey rowKey = (TreeRowKey) key;
//Object savedRowKey = input.getRowKey();
try {
input.captureOrigValue();
input.setRowKey(context, key);
RendererDataModelEventNavigator levelNavigator = new RendererDataModelEventNavigator(input, rowKey, context,
droppedDownToLevelFlag);
final TreeRange stateRange = (TreeRange) input.getComponentState().getRange();
TreeRange treeRange = new TreeRange() {
public boolean processChildren(TreeRowKey rowKey) {
return stateRange.processChildren(rowKey);
}
public boolean processNode(TreeRowKey rowKey) {
Object currentKey = input.getRowKey();
if (currentKey == null ? rowKey != null : !currentKey.equals(rowKey)) {
//currentKey NE rowKey
input.setRowKey(context, rowKey);
}
UITreeNode nodeFacet = input.getNodeFacet();
if (!nodeFacet.isRendered()) {
return false;
}
return stateRange.processNode(rowKey);
}
};
input.transferQueuedNode();
//TODO should render if current node not in range?
input.walk(context, new DataVisitorWithLastElement(droppedDownToLevelFlag, input,
levelNavigator, key), treeRange, key, null);
levelNavigator.followRowKey(context, null);
} finally {
input.setRowKey(context, null);
input.restoreOrigValue();
}
}
}
class Flag {
private Context context;
public Context getContext() {
return context;
}
public void setContext(Context context) {
this.context = context;
}
}
class Context {
private String clientId;
private Object rowKey;
private boolean expanded;
private boolean last;
private boolean hasChildren = true;
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public Object getRowKey() {
return rowKey;
}
public void setRowKey(Object rowKey) {
this.rowKey = rowKey;
}
public boolean isExpanded() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
public boolean isLast() {
return last;
}
public void setLast(boolean last) {
this.last = last;
}
public boolean isHasChildren() {
return hasChildren;
}
public void setHasChildren(boolean hasChildren) {
this.hasChildren = hasChildren;
}
}