blob: e8fc3a7728641bf8373b28c44d3a63ab4f006750 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.trinidadinternal.renderkit.core.xhtml;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.apache.myfaces.trinidad.bean.FacesBean;
import org.apache.myfaces.trinidad.bean.PropertyKey;
import org.apache.myfaces.trinidad.component.UIXHierarchy;
import org.apache.myfaces.trinidad.component.UIXTree;
import org.apache.myfaces.trinidad.component.core.data.CoreTree;
import org.apache.myfaces.trinidad.context.Agent;
import org.apache.myfaces.trinidad.context.FormData;
import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidad.context.RequestContext;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.model.RowKeySet;
import org.apache.myfaces.trinidad.skin.Icon;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.TreeUtils;
/**
* Renderer for trees.
*
*/
public class TreeRenderer extends XhtmlRenderer
{
public TreeRenderer()
{
this(CoreTree.TYPE);
}
protected TreeRenderer(
FacesBean.Type type)
{
super(type);
}
@Override
protected void findTypeConstants(
FacesBean.Type type)
{
super.findTypeConstants(type);
_immediateKey = type.findKey("immediate");
}
@Override
public boolean getRendersChildren()
{
return true;
}
/**
* @todo do not mess with selection here. queue an event.
*/
@SuppressWarnings("unchecked")
@Override
protected void decode(
FacesContext facesContext,
UIComponent component,
@SuppressWarnings("unused")
FacesBean facesBean,
String clientId)
{
Map<String, String> parameters =
facesContext.getExternalContext().getRequestParameterMap();
String source = parameters.get(XhtmlConstants.SOURCE_PARAM);
if (clientId == null)
{
clientId = getClientId(facesContext, component);
}
if (!clientId.equals(source))
return;
TreeUtils.decodeExpandEvents(parameters, component,
Collections.emptyList());
String currencyStrParam =
source + NamingContainer.SEPARATOR_CHAR + SELECTED_PARAM;
String currencyStr = parameters.get(currencyStrParam);
if ((currencyStr != null) && (!"".equals(currencyStr)))
{
UIXTree tree = (UIXTree) component;
Object oldPath = tree.getRowKey();
tree.setClientRowKey(currencyStr);
tree.getSelectedRowKeys().clear();
tree.getSelectedRowKeys().add();
tree.setRowKey(oldPath);
}
RequestContext.getCurrentInstance().addPartialTarget(component);
}
@Override
protected void encodeAll(
FacesContext context,
RenderingContext rc,
UIComponent component,
FacesBean bean
) throws IOException
{
// Since Train is a naming container, we can be more
// efficient about skipping its children
if (!PartialPageUtils.containsPprTargets(rc,
component,
getClientId(context, component)))
{
return;
}
UIXHierarchy tree = (UIXHierarchy) component;
TreeUtils.expandFocusRowKey((UIXTree) component);
Object oldPath = tree.getRowKey();
try
{
boolean continueRendering = setInitialPath(tree, bean);
if (!continueRendering)
return;
_renderContent(context, rc, tree, bean);
}
finally
{
tree.setRowKey(oldPath);
}
}
@Override
protected boolean shouldRenderId(
FacesContext context,
UIComponent component)
{
return true;
}
private void _renderContent(
FacesContext context,
RenderingContext rc,
UIXHierarchy tree,
FacesBean bean
) throws IOException
{
FormData fd = rc.getFormData();
if (fd == null)
{
_LOG.warning("TREE_COMPONENT_MUST_INSIDE_FORM");
return;
}
ResponseWriter rw = context.getResponseWriter();
rw.startElement("div", tree);
renderId(context, tree);
renderAllAttributes(context, rc, tree, bean);
final String id = getClientId(context, tree);
UIComponent stamp = getFacet(tree, CoreTree.NODE_STAMP_FACET);
//@todo - will this tree.getFocusPath survive?
// List focusPath = getFocusPath(context, node);
Object focusPath = tree.getFocusRowKey();
String formName = fd.getName();
// Bug 3931544: don't use colons in Javascript variable names.
// We'll just replace colons with underscores; not perfect, but adequate
final String varName = "_adftree" + XhtmlUtils.getJSIdentifier(id);
boolean leftToRight = !rc.isRightToLeft();
int rootSize = tree.getRowCount();
RowKeySet state = getExpandedRowKeys(tree);
Map<Object, Boolean> selectedPaths = getSelectedPaths(focusPath);
// render each of the root nodes
for (int i = 0; i < rootSize; i++)
{
tree.setRowIndex(i);
_renderNode(context, rc, tree, bean, stamp, varName, state,
selectedPaths, new Boolean[_DEFAULT_TREE_DEPTH],
leftToRight, (i == 0), (i == rootSize - 1), 0);
}
//HKuhn - not needed in printable mode (scripting disabled)
if (supportsScripting(rc))
{
rw.startElement("script", null);
renderScriptDeferAttribute(context, rc);
renderScriptTypeAttribute(context, rc);
_renderTreeJS(context, rc, tree, bean);
//out.writeText("_setNodes('"+name+"','"+nodesRendered+"');");
String selectedParam =
id + NamingContainer.SEPARATOR_CHAR + SELECTED_PARAM;
String focusNodeId = TreeUtils.getFocusNodeClientId(context, tree);
rw.writeText("var " + varName + " = " +
_createNewJSSelectionState(formName, id, selectedParam,
focusNodeId), null);
rw.endElement("script");
fd.addNeededValue(selectedParam);
}
rw.endElement("div");
fd.addNeededValue(_PATH_PARAM);
}
// return whether to continue with rendering
protected boolean setInitialPath(
UIXHierarchy tree,
FacesBean bean)
{
tree.setRowKey(null);
return true;
}
private boolean _isShownSelected(
UIXHierarchy tree,
Map<Object, Boolean> selectedPaths,
Object currPath)
{
boolean selected = false;
if (tree instanceof UIXTree)
selected = ((UIXTree) tree).getSelectedRowKeys().isContained();
if (selected)
return true;
Object value = selectedPaths.get(currPath);
if (value != null)
return true;
return false;
}
protected Map<Object, Boolean> getSelectedPaths(
Object focusPath)
{
if (focusPath == null)
return new HashMap<Object, Boolean>(0);
Map<Object, Boolean> selectedPaths = new HashMap<Object, Boolean>(1);
selectedPaths.put(focusPath, Boolean.TRUE);
return selectedPaths;
}
protected RowKeySet getExpandedRowKeys(
UIXHierarchy tree)
{
return ((UIXTree) tree).getDisclosedRowKeys();
}
/**
* Returns true if the tree connecting lines should be rendered
* @param rc the RenderingContext
* @return the value of the AF_TREE_SHOW_LINES skin property
*/
protected boolean isShowLines(
RenderingContext rc)
{
Object showLines = rc.getSkin().getProperty(SkinProperties.AF_TREE_SHOW_LINES);
return showLines == null || showLines.equals(Boolean.TRUE);
}
/**
* Returns the URI of the vertical line icon
*
* @param context the FacesContext
* @param rc the RenderingContext
* @param isLine if there is need for a vertical line
* @param leftToRight left to right
* @return the URI of the icon
*/
protected String getConnectingBackgroundIcon(
FacesContext context,
RenderingContext rc,
boolean isLine,
boolean leftToRight)
{
if (!isLine || !isShowLines(rc))
return null;
Icon icon = rc.getIcon(SkinSelectors.AF_TREE_LINE_ICON);
return (icon == null) ? null : icon.getImageURI(context, rc).toString();
}
/**
* Returns the URI of the expanded-icon's background icon
* Usually the lines connecting a node
* @param context the FacesContext
* @param rc the RenderingContext
* @param isLastSibling true if the node is the last child of its parent node
* @param isLeftToRight left to right
* @return the URI of the icon
*/
protected String getIconBackgroundIcon(
FacesContext context,
RenderingContext rc,
boolean isLastSibling,
boolean isLeftToRight)
{
Object showLines = rc.getSkin().getProperty(SkinProperties.AF_TREE_SHOW_LINES);
if (!isShowLines(rc))
return null;
Icon nodeBackgroundIcon = rc.getIcon(isLastSibling
? SkinSelectors.AF_TREE_LINE_LAST_ICON : SkinSelectors.AF_TREE_LINE_MIDDLE_ICON);
return (nodeBackgroundIcon != null)
? nodeBackgroundIcon.getImageURI(context, rc).toString() : null;
}
// render the correct icon for a specific node
protected void renderExpandCell(
FacesContext context,
RenderingContext rc,
UIXHierarchy tree,
int expanded,
boolean isLastSibling,
String onclick
) throws IOException
{
Icon icon = null;
String iconURI = null;
String backgroundIconURI;
String nodeBackgroundIconURI;
boolean isAbsoluteImageURI = true;
String iconHeight = null;
Object altText = null;
String text = null;
boolean isMacOS =
Agent.PLATFORM_MACOS.equals(rc.getAgent().getPlatformName());
// add in the expandability
switch (expanded)
{
case NO_CHILDREN:
icon = rc.getIcon(SkinSelectors.AF_TREE_NO_CHILDREN_ICON);
if (icon == null || icon.getImageURI(context, rc) == null)
{
iconURI = TRANSPARENT_GIF;
iconHeight = _ICON_HEIGHT;
isAbsoluteImageURI = false;
}
break;
case EXPAND_CLOSED:
// "\u21D2"; // Double Arrow right
if (isMacOS)
// single arrow left
text = rc.isRightToLeft() ? "\u2190" : "\u2192"; // single arrow right
else if (isPDA(rc))
//for PDAs use a simple "+" or "-" since miscellaneous unicode characters
//are not supported
text = "[+]"; //plus sign
else // triangle left
text = rc.isRightToLeft() ? "\u25C4" : "\u25BA"; // triangle right
altText = rc.getTranslatedString(_EXPAND_TIP_KEY);
icon = rc.getIcon(SkinSelectors.AF_TREE_COLLAPSED_ICON);
break;
case EXPAND_OPEN:
//"\u21D3"; // double arrow down
if (isMacOS)
text = "\u2193"; // single arrow down
else if (isPDA(rc))
//for PDAs use a simple "+" or "-" since miscellaneous unicode characters
//are not supported
text = "[-]"; //plus sign
else
text = "\u25BC"; // triangle down
altText = rc.getTranslatedString(_COLLAPSE_TIP_KEY);
icon = rc.getIcon(SkinSelectors.AF_TREE_EXPANDED_ICON);
break;
case EXPAND_ALWAYS:
if (isMacOS)
text = "\u2193"; // single arrow down
else if (isPDA(rc))
text = "[-]"; //plus sign
else
text = "\u25BC"; // triangle down
//for PDAs use a simple "+" or "-" since miscellaneous unicode character
//s are not supported
altText = rc.getTranslatedString(_DISABLED_COLLAPSE_TIP_KEY);
icon = rc.getIcon(SkinSelectors.AF_TREE_EXPANDED_ICON);
break;
}
if (iconURI == null && icon != null)
{
//This can be null so we need to check for it before doing toString
Object o = icon.getImageURI(context, rc);
if(o != null)
{
iconURI = o.toString();
}
}
backgroundIconURI = getConnectingBackgroundIcon(context, rc, !isLastSibling, true);
nodeBackgroundIconURI = getIconBackgroundIcon(context, rc, isLastSibling, true);
if (iconURI != null)
{
renderExpandIconCell(context, rc,
backgroundIconURI, nodeBackgroundIconURI,
iconURI, isAbsoluteImageURI,
altText, _ICON_WIDTH, iconHeight, onclick);
} else
{
_renderTextCell(context, rc, tree, expanded, text, altText, _ICON_WIDTH, onclick,
SkinSelectors.TREE_DISCLOSED_SYMBOL_STYLE_CLASS);
}
}
private void _renderTextCell(
FacesContext context,
RenderingContext rc,
UIXHierarchy tree,
int expanded,
String text,
Object altText,
String width,
String onclick,
String styleClass
) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
writer.writeAttribute(XhtmlConstants.WIDTH_ATTRIBUTE, width, null);
writer.writeAttribute("title", altText, null);
renderStyleClass(context, rc, styleClass);
boolean jsSupport = supportsScripting(rc);
if (onclick != null)
{
if (jsSupport)
{
writer.startElement(XhtmlConstants.LINK_ELEMENT, null);
writer.writeAttribute(XhtmlConstants.HREF_ATTRIBUTE, "#", null);
writer.writeAttribute(XhtmlConstants.ONCLICK_ATTRIBUTE, onclick, null);
}
else
{
// For Non-JavaScript browsers, render an input element(type= submit) to
// submit the page. Encode the name attribute with the parameter name
// and value thus it would enable the browsers to include the name of
// this element in its payLoad if it submits the page.
String nameAttr = TreeUtils.renderEncodedNameAttri(
context,
rc,
tree,
getClientId(context, tree),
expanded == EXPAND_CLOSED);
writer.startElement("input", null);
writer.writeAttribute("type", "submit",null);
writer.writeAttribute("name", nameAttr, null);
writer.writeAttribute("value", text, null);
}
}
if (text != null && jsSupport)
writer.writeText(text, null);
if (onclick != null)
{
if (jsSupport)
{
writer.endElement(XhtmlConstants.LINK_ELEMENT);
}
else
{
writer.endElement("input");
}
}
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
}
protected void renderExpandIconCell(
FacesContext context,
RenderingContext rc,
String backgroundIcon,
String nodeBackgroundIcon,
String icon,
boolean isIconAbsoluteURI,
Object altText,
String width,
String height,
String onclick
) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
writer.writeAttribute(XhtmlConstants.WIDTH_ATTRIBUTE, width, null);
writer.writeAttribute(XhtmlConstants.HEIGHT_ATTRIBUTE, "100%", null);
writer.writeAttribute(XhtmlConstants.VALIGN_ATTRIBUTE, XhtmlConstants.V_ALIGN_TOP,
null);
if (backgroundIcon != null)
{
// String backgroundIconURI = getAbsoluteImageUri(context, rc, backgroundIcon);
writer.writeAttribute(XhtmlConstants.STYLE_ATTRIBUTE,
_BACKGROUND_IMAGE_URL + backgroundIcon +
_END_FUNC, null);
}
if (nodeBackgroundIcon != null)
{
writer.startElement(XhtmlConstants.DIV_ELEMENT, null);
writer.writeAttribute(XhtmlConstants.STYLE_ATTRIBUTE,
_BACKGROUND_IMAGE_URL + nodeBackgroundIcon +
_END_FUNC + _BACKGROUND_NO_REPEAT, null);
}
if (onclick != null)
{
writer.startElement(XhtmlConstants.LINK_ELEMENT, null);
writer.writeAttribute(XhtmlConstants.HREF_ATTRIBUTE, "#", null);
writer.writeAttribute(XhtmlConstants.ONCLICK_ATTRIBUTE, onclick, null);
}
_renderIcon(context, rc, icon, isIconAbsoluteURI, altText, null, height);
if (onclick != null)
{
writer.endElement(XhtmlConstants.LINK_ELEMENT);
}
if (nodeBackgroundIcon != null)
{
writer.endElement(XhtmlConstants.DIV_ELEMENT);
}
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
}
protected void renderIconCell(
FacesContext context,
RenderingContext rc,
UIXHierarchy tree,
String backgroundIcon,
String icon,
boolean isIconAbsoluteURI,
Object altText,
String width,
String height,
String onclick
) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
writer.writeAttribute(XhtmlConstants.WIDTH_ATTRIBUTE, width, null);
if (backgroundIcon != null)
{
String backgroundIconURI = backgroundIcon;//getAbsoluteImageUri(context, rc, backgroundIcon);
writer.writeAttribute(XhtmlConstants.STYLE_ATTRIBUTE,
_BACKGROUND_IMAGE_URL + backgroundIconURI +
_END_FUNC, null);
}
if (onclick != null)
{
writer.startElement(XhtmlConstants.LINK_ELEMENT, null);
writer.writeAttribute(XhtmlConstants.HREF_ATTRIBUTE, "#", null);
writer.writeAttribute(XhtmlConstants.ONCLICK_ATTRIBUTE, onclick, null);
String treeName = getClientId(context, tree);
String id = treeName + NamingContainer.SEPARATOR_CHAR + "lnk";
writer.writeAttribute(XhtmlConstants.ID_ATTRIBUTE, id, null);
}
_renderIcon(context, rc, icon, isIconAbsoluteURI, altText, width, height);
if (onclick != null)
{
writer.endElement(XhtmlConstants.LINK_ELEMENT);
}
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
}
private static String _createFocusNodeGetter(
String focusNodeId)
{
return (focusNodeId != null) ? "document.getElementById('" + focusNodeId + "')" : null;
}
private static String _callJSSelect(
UIXHierarchy tree,
String jsVarName)
{
String currencyStr = tree.getClientRowKey();
return jsVarName + ".select(this,'" + currencyStr + "');";
}
private String _createNewJSSelectionState(
String formName,
String treeClientId,
String selectParam,
String focusNodeId)
{
String treeState = TreeUtils.createNewJSCollectionComponentState(formName, treeClientId);
String focusNode = _createFocusNodeGetter(focusNodeId);
String jsSelectionState = null;
if (focusNode == null)
{
jsSelectionState = "new _adfTreeSelector('" + selectParam + "'," + treeState + ");";
}
else
{
jsSelectionState = "new _adfTreeSelector('" + selectParam + "'," + treeState + "," + focusNode + ");";
}
return jsSelectionState;
}
private void _renderTreeJS(
FacesContext context,
RenderingContext rc,
UIComponent component,
FacesBean bean
) throws IOException
{
if (!rc.getProperties().containsKey(_JS_RENDERED_KEY))
{
rc.getProperties().put(_JS_RENDERED_KEY, Boolean.TRUE);
ResponseWriter writer = context.getResponseWriter();
writer.writeText("function _adfTreeSelector(selectParam,tState,focusNode) {" +
"this._selectParam = selectParam;" +
"this.treeState = tState;" +
"this._pTag = focusNode;" +
"}" +
"_adfTreeSelector.prototype.select = function(tag,path) {" +
"if (this._pTag != null) {" +
"this._pTag.className='" + SkinSelectors.TREE_ROW_STYLE_CLASS +
"';" + "}" + "this._pTag = tag;" +
//if there are any problems see TRINIDAD-935
"tag.className='" + SkinSelectors.TREE_ROW_SELECTED_STYLE_CLASS +
"';" + "};", null);
// _setSelection(..) and _getSelection(..) are called by the
// ClientStateTreeDataProxy (if selection is enabled for this particular
// tree). _setSelection is called to set the initial selection (as
// determined by the proxy). _getSelection is called to get at the
// current selection state (to send back to the server by the proxy)
// @param source the name or ID of the tree
// @param sel something that identifies the current selected node
// _setSelection(source,sel)
// @param source the name or ID of the tree
// @return something that identifies the current selected node
// _getSelection(source)
// writer.writeText
// ("var _treeSel = new Object();" +
// "var _treeNodes = new Object();" +
// "function _setSelection(source,sel) {"
// + "_treeSel[source]=sel;" +
// "}" +
// "function _getSelection(source) {"
// + "return _treeSel[source];" +
// "}" +
//
// // _setNodes is used to indicate the number of tree nodes that are
// // currently visible. This is so that _clearSelection knows exactly
// // how many elements to clear
// "function _setNodes(source,nodes) {"
// + "_treeNodes[source]=nodes;" +
// "}" +
// "function _getNodes(source) {"
// + "return _treeNodes[source];" +
// "}",
// null );
//
// writer.writeText
// ("function _select(name,index,nodeID) {"
// + "_clearSelection(name);"
// + "var e =_getElementById(document,name+index);"
// + "e.className = '",
// null );
// writer.writeText(TREE_ROW_SELECTED_STYLE_CLASS+"';", null);
// writer.writeText
// (" _setSelection(name,nodeID);return true;" +
// "}",
// null );
//
// writer.writeText
// ("function _clearSelection(name) {"
// + "var sz = _getNodes(name);"
// + "for (var i = 0; i < sz; i++) {"
// + "var e =_getElementById(document,name+i);"
// + "e.className='",
// null);
// writer.writeText(TREE_ROW_STYLE_CLASS+"';", null);
// writer.writeText
// (" }" +
// "}",
// null);
boolean immediate = getImmediate(component, bean);
String buff =
TreeUtils.setupJSTreeCollectionComponent(!immediate) + ";";
writer.writeText(buff, null);
}
}
// render one row of the tree
private void _renderNode(
FacesContext context,
RenderingContext rc,
UIXHierarchy tree,
FacesBean bean,
UIComponent stamp,
final String varName,
RowKeySet state,
Map<Object, Boolean> selectedPaths,
Boolean[] prepend,
boolean leftToRight,
boolean isFirstSibling,
boolean isLastSibling,
int nodeDepth
) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
// each row is a table
writer.startElement(XhtmlConstants.TABLE_ELEMENT, null);
OutputUtils.renderLayoutTableAttributes(context, rc, "0", "0", "0", null);
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
// render the prepend
_prependIcons(context, rc, tree, prepend, leftToRight);
String onclickExpand = null;
int expand = _getExpandValue(tree, state);
if ((expand != NO_CHILDREN) && supportsNavigation(rc))
{
onclickExpand =
TreeUtils.callJSExpandNode(tree, varName + ".treeState",
(expand == EXPAND_CLOSED));
}
renderExpandCell(context, rc, tree, expand, isLastSibling, onclickExpand);
// DataObject curData = BeanAdapterUtils.getAdapter(context, tree.getRowData());
String treeStyle = SkinSelectors.TREE_ROW_STYLE_CLASS;
// location was a colon separated list of IDs
//boolean selected = proxy.isSelected(context, node, location);
Object currPath = tree.getRowKey();
boolean selected = _isShownSelected(tree, selectedPaths, currPath);
String onClick = _callJSSelect(tree, varName);
// if ( proxy.selectionEnabled(context) )
// {
// // selection with the proxy doesn't work on netscape
// // filed as bug 1817185 - so far we have not figured
// // out a way without using layers and we are seeing nodes
// // jump around with layers so disabling selection on netscape
// if ( isNetscape(context) )
// selected = false;
// else
// {
// if (supportsNavigation(context))
// onClick = "return _select('" + treename + "'," + renderedIndex +
// ",'" + location + "');";
// }
// }
if (selected)
{
treeStyle = SkinSelectors.TREE_ROW_SELECTED_STYLE_CLASS;
}
renderNodeIconCell(context, rc, tree, expand);
// render space between icon and node stamp
// alt Text
renderIconCell(context, rc, tree, null, TRANSPARENT_GIF, false, null,
_NODE_SPACER, _ICON_HEIGHT, null);
// render the node stamp
renderStampCell(context, rc, tree, stamp, onClick, treeStyle, nodeDepth);
// end row
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
//end table
writer.endElement(XhtmlConstants.TABLE_ELEMENT);
// render children
if ((expand == EXPAND_OPEN) || (expand == EXPAND_ALWAYS))
{
_renderNodeChildren(context, rc, tree, bean, stamp, varName, state, selectedPaths,
prepend, leftToRight, isFirstSibling, isLastSibling, nodeDepth);
}
}
protected void renderNodeIconCell(
FacesContext context,
RenderingContext rc,
UIXHierarchy tree,
int expand
) throws IOException
{
String nodeType = getNodeType(tree);
Icon nodeIcon = getNodeIcon(rc, nodeType, expand);
ResponseWriter writer = context.getResponseWriter();
// render the node icon
if (nodeIcon != null)
{
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
writer.writeAttribute(XhtmlConstants.WIDTH_ATTRIBUTE, _ICON_WIDTH, null);
writer.writeAttribute(XhtmlConstants.HEIGHT_ATTRIBUTE, _ICON_HEIGHT, null);
writer.writeAttribute(XhtmlConstants.VALIGN_ATTRIBUTE, XhtmlConstants.V_ALIGN_TOP,
null);
_renderIcon(context, rc, nodeIcon.getImageURI(context, rc).toString(),
true, null, null, null);
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
}
}
protected String getNodeType(
UIXHierarchy tree)
{
String nodeType = null;
Object rowData = tree.getRowData();
Class rowClass = rowData.getClass();
Method method;
try
{
method = retrieveGetNodeTypeMethod(rowClass);
if (method != null && method.getReturnType().equals(String.class))
{
nodeType = (String) method.invoke(rowData);
}
}
catch (IllegalAccessException e) { ; }
catch (InvocationTargetException e) { ; }
return nodeType;
}
private Method retrieveGetNodeTypeMethod(Class rowClass)
{
try {
synchronized (NODE_TYPE_METHOD_CACHE) {
if (NODE_TYPE_METHOD_CACHE.containsKey(rowClass))
{
return NODE_TYPE_METHOD_CACHE.get(rowClass);
}
Method getNodeTypeMethod = rowClass.getMethod("getNodeType");
NODE_TYPE_METHOD_CACHE.put(rowClass, getNodeTypeMethod);
return getNodeTypeMethod;
}
} catch (Exception e)
{ return null; }
}
protected String getNodeIconSelector(
String nodeType,
int expandedState)
{
switch (expandedState)
{
case EXPAND_OPEN:
nodeType += NODE_ICON_EXPANDED_SUFFIX;
break;
case EXPAND_CLOSED:
nodeType += NODE_ICON_COLLAPSED_SUFFIX;
break;
}
return SkinSelectors.AF_TREE_NODE_ICON + ":" + nodeType;
}
protected Icon getNodeIcon(
RenderingContext rc,
String nodeType,
int expandedState
)
{
if (nodeType == null || nodeType.length() == 0)
{
return null;
}
Icon icon = rc.getIcon(getNodeIconSelector(nodeType, expandedState));
if (icon == null)
if (expandedState != NO_CHILDREN)
{
icon = rc.getIcon(getNodeIconSelector(nodeType, NO_CHILDREN));
}
else
{
icon = rc.getIcon(getNodeIconSelector(nodeType, EXPAND_CLOSED));
}
return icon;
}
protected void renderStampCell(
FacesContext context,
RenderingContext rc,
UIXHierarchy tree,
UIComponent stamp,
String onClick,
String treeStyle,
int nodeDepth
) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
writer.writeAttribute(XhtmlConstants.NOWRAP_ATTRIBUTE, Boolean.FALSE, null);
renderStyleClass(context, rc, SkinSelectors.TREE_NODE_ADJUST_STYLE_CLASS);
writer.startElement(XhtmlConstants.SPAN_ELEMENT, null);
writer.writeAttribute(XhtmlConstants.ID_ATTRIBUTE, stamp.getClientId(), null);
renderStyleClass(context, rc, treeStyle);
if (supportsScripting(rc))
{
writer.writeAttribute(XhtmlConstants.ONCLICK_ATTRIBUTE, onClick, null);
}
// if screen reader mode render the stamp with level of node from root
_renderStampBasedOnAccessibilty(context, rc, stamp, nodeDepth);
writer.endElement(XhtmlConstants.SPAN_ELEMENT);
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
}
private void _renderNodeChildren(
FacesContext context,
RenderingContext rc,
UIXHierarchy tree,
FacesBean bean,
UIComponent stamp,
final String varName,
RowKeySet state,
Map<Object, Boolean> selectedPaths,
Boolean[] prepend,
boolean leftToRight,
boolean isFirstSibling,
boolean isLastSibling,
int nodeDepth
) throws IOException
{
tree.enterContainer();
int childCount = tree.getRowCount();
if (childCount > 0)
{
// prepare the prepended icons for the child nodes
prepend = _appendIcon(prepend, (isLastSibling) ? Boolean.FALSE : Boolean.TRUE);
Boolean[] currClone;
++nodeDepth; // increment the depth of the child from the root
int oldIndex = tree.getRowIndex();
for (int i = 0; i < childCount; i++)
{
currClone = new Boolean[prepend.length];
System.arraycopy(prepend, 0, currClone, 0, prepend.length);
tree.setRowIndex(i);
_renderNode(context, rc, tree, bean, stamp, varName, state,
selectedPaths, currClone, leftToRight, i == 0,
(i == childCount - 1), nodeDepth);
}
tree.setRowIndex(oldIndex);
--nodeDepth;
}
tree.exitContainer();
}
// is this row childless, open, or closed?
private int _getExpandValue(
UIXHierarchy tree,
RowKeySet state)
{
if (tree.isContainer())
{
if (state.isContained())
return EXPAND_OPEN;
else
return EXPAND_CLOSED;
}
return NO_CHILDREN;
}
// render an icon with our own special formatting
private void _renderIcon(
FacesContext context,
RenderingContext rc,
String icon,
boolean isIconAbsoluteURI,
Object text,
String width,
String height
) throws IOException
{
if (icon != null)
{
ResponseWriter writer = context.getResponseWriter();
// TODO: change
writer.startElement("img", null);
renderStyleClass(context, rc, SkinSelectors.TREE_ICON_STYLE_CLASS);
if (width != null)
writer.writeAttribute(XhtmlConstants.WIDTH_ATTRIBUTE, width, null);
if (height != null)
writer.writeAttribute(XhtmlConstants.HEIGHT_ATTRIBUTE, height, null);
// Convert iconURL to an absolute uri
if (!isIconAbsoluteURI)
icon = getAbsoluteImageUri(context, rc, icon);
renderEncodedResourceURI(context, "src", icon);
// Ensure that we're never rendering null; see bug 4161181
// why this logic is not in renderAltAndTooltipForImage().
// This is, in essence, re-introducing a more restricted version
// of that bug.
OutputUtils.renderAltAndTooltipForImage(context, rc, text == null? "": text);
writer.writeAttribute("border", "0", null);
writer.endElement("img");
}
}
// add a boolean flag to the chain of icons.
// the chain is rendered before each icon
private Boolean[] _appendIcon(
Boolean[] prepend,
Boolean isLine)
{
int currLength = prepend.length;
if (prepend[currLength - 1] != null)
{
// resize, incrementing should be fine
Boolean[] newBools =
new Boolean[currLength + _DEFAULT_TREE_INCREMENT];
System.arraycopy(prepend, 0, newBools, 0, currLength);
prepend = newBools;
}
for (int i = 0; i < currLength; i++)
{
if (prepend[i] == null)
{
prepend[i] = isLine;
break;
}
}
return prepend;
}
private void _prependIcons(
FacesContext context,
RenderingContext rc,
UIXHierarchy tree,
Boolean[] prepend,
boolean leftToRight
) throws IOException
{
int currLength = prepend.length;
Boolean isLine;
for (int i = 0; i < currLength; i++)
{
isLine = prepend[i];
if (isLine != null)
{
String icon = TRANSPARENT_GIF;
String backgroundIcon =
getConnectingBackgroundIcon(context, rc, isLine.booleanValue(), leftToRight);
// alt text
renderIconCell(context, rc, tree, backgroundIcon, icon, false, null,
_ICON_WIDTH, "100%", null);
}
}
}
protected boolean getImmediate(
UIComponent component,
FacesBean bean)
{
Object o = bean.getProperty(_immediateKey);
if (o == null)
o = _immediateKey.getDefault();
return Boolean.TRUE.equals(o);
}
protected String getDefaultIconName()
{
return null;
}
private void _renderStampBasedOnAccessibilty(
FacesContext context,
RenderingContext rc,
UIComponent stamp,
int depth
) throws IOException
{
if (isScreenReaderMode(rc))
{
RenderingContext arc = RenderingContext.getCurrentInstance();
FacesContext fc = FacesContext.getCurrentInstance();
if (rc.isRightToLeft())
{
//TODO: do we need default stamp support???
encodeChild(context, stamp);
TreeUtils.writeNodeLevel(fc, arc, depth, _NODE_LEVEL_TEXT_KEY);
}
else
{
TreeUtils.writeNodeLevel(fc, arc, depth, _NODE_LEVEL_TEXT_KEY);
encodeChild(context, stamp);
}
}
else
encodeChild(context, stamp);
}
private static final String _BACKGROUND_IMAGE_URL =
"background-image:url(";
private static final String _END_FUNC = ");";
private static final String _BACKGROUND_NO_REPEAT = "background-repeat:no-repeat;";
private static final String _ICON_WIDTH = "19";
private static final String _ICON_HEIGHT = "18";
private static final String _NODE_SPACER = "3";
// expanded states
protected static final int NO_CHILDREN = 0;
protected static final int EXPAND_CLOSED = 1;
protected static final int EXPAND_OPEN = 2;
protected static final int EXPAND_ALWAYS = 3;
// prepend chain constants
private static final int _DEFAULT_TREE_DEPTH = 10;
private static final int _DEFAULT_TREE_INCREMENT = 5;
// =-= ACW: this key is used to make sure that certain javascript functions
// used by this renderer, are rendered only once per render cycle.
private static final Object _JS_RENDERED_KEY = new Object();
// Key used by StyledTextBean to query style class
static final String _STYLE_CLASS_KEY = "_styleClass";
private final Map<Class<?>, Method> NODE_TYPE_METHOD_CACHE = new HashMap<Class<?>, Method>();
private PropertyKey _immediateKey;
// translation keys
private static final String _DISABLED_COLLAPSE_TIP_KEY =
"af_tree.DISABLED_COLLAPSE_TIP";
private static final String _COLLAPSE_TIP_KEY = "af_tree.COLLAPSE_TIP";
private static final String _EXPAND_TIP_KEY = "af_tree.EXPAND_TIP";
private static final String _NODE_LEVEL_TEXT_KEY = "af_tree.NODE_LEVEL";
private static final String _PATH_PARAM = "path";
public static final String SELECTED_PARAM = "_selected";
public static final String NODE_ICON_EXPANDED_SUFFIX = "-expanded";
public static final String NODE_ICON_COLLAPSED_SUFFIX = "-collapsed";
private static final TrinidadLogger _LOG =
TrinidadLogger.createTrinidadLogger(TreeRenderer.class);
}