/*
* 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.desktop;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
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.CollectionComponent;
import org.apache.myfaces.trinidad.component.UIXHierarchy;
import org.apache.myfaces.trinidad.component.UIXTree;
import org.apache.myfaces.trinidad.component.UIXTreeTable;
import org.apache.myfaces.trinidad.component.core.data.CoreTreeTable;
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.render.CoreRenderer;
import org.apache.myfaces.trinidad.skin.Icon;
import org.apache.myfaces.trinidad.util.IntegerUtils;
import org.apache.myfaces.trinidad.render.XhtmlConstants;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.OutputUtils;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.ResourceKeyUtils;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.SkinSelectors;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.TrinidadRenderingConstants;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.CellUtils;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.ColumnData;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.FocusColumnRenderer;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.RowData;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.SpecialColumnRenderer;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.TableRenderingContext;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.TableUtils;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.TreeNodeColumnRenderer;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.TreeTableNavRenderer;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.TreeTableRenderingContext;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.table.TreeUtils;
/**
* Renderer for treeTable
* <p>
* @version $Name: $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/renderkit/core/desktop/TreeTableRenderer.java#0 $) $Date: 10-nov-2005.19:03:37 $
*/
public class TreeTableRenderer extends DesktopTableRenderer
{
public TreeTableRenderer()
{
super(CoreTreeTable.TYPE);
}
@Override
protected void findTypeConstants(FacesBean.Type type)
{
super.findTypeConstants(type);
_immediateKey = type.findKey("immediate");
_expandAllEnabledKey = type.findKey("expandAllEnabled");
_rootNodeRendered = type.findKey("rootNodeRendered");
}
/**
* @todo Set "expanded" vs. "collapsed" correctly on the queued
* DisclosureEvent
* @todo deal with null and exceptions
*/
@SuppressWarnings("unchecked")
@Override
public void decode(
FacesContext context,
UIComponent component)
{
decodeSelection(context, component);
Map<String, String> parameters =
context.getExternalContext().getRequestParameterMap();
Object source = parameters.get(TrinidadRenderingConstants.SOURCE_PARAM);
if (component.getClientId(context).equals(source))
{
UIXTreeTable treeTable = (UIXTreeTable) component;
TreeUtils.decodeExpandEvents(parameters, component,
TreeUtils.getFocusRowKey(treeTable));
TreeUtils.decodeFocusEvent(parameters, component);
TreeUtils.decodeGotoEvent(parameters, component);
// bug 4522210:
RequestContext.getCurrentInstance().addPartialTarget(component);
}
}
/**
* Creates the correct subclass of the TableRenderingContext to
* use for this Renderer.
*/
@Override
protected TableRenderingContext createRenderingContext(
FacesContext context,
RenderingContext arc,
UIComponent component
)
{
return new TreeTableRenderingContext(context, arc, component);
}
/**
* Render an empty table, if necessary.
* @return true if the table was empty, and an alternative empty
* version was shown, false otherwise.
*/
@Override
protected boolean renderTableWithoutColumns(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component) throws IOException
{
return false;
}
/**
* render all pieces of the table
*/
@Override
protected void encodeAll(
FacesContext context,
RenderingContext arc,
UIComponent component,
FacesBean bean) throws IOException
{
FormData fData = arc.getFormData();
if (fData != null)
{
fData.addNeededValue(TrinidadRenderingConstants.PARTIAL_TARGETS_PARAM);
fData.addNeededValue(TrinidadRenderingConstants.PARTIAL_PARAM);
}
// we cannot render without a nodeStamp:
if (((UIXTreeTable) component).getNodeStamp() == null)
{
_LOG.warning("NODESTAMP_FACET_MISSING");
return;
}
TreeUtils.setDefaultFocusRowKey((UIXTree) component);
TreeUtils.expandFocusRowKey((UIXTree) component);
super.encodeAll(context, arc, component, bean);
// have we rendered the script before?
// and are we not in printable mode (scripting disabled)?
if (arc.getProperties().put(_JS_LIBS_KEY, Boolean.TRUE) == null
&& supportsScripting(arc))
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement(XhtmlConstants.SCRIPT_ELEMENT, null);
renderScriptDeferAttribute(context, arc);
// Bug #3426092:
// render the type="text/javascript" attribute in accessibility mode
renderScriptTypeAttribute(context, arc);
boolean validate = !isImmediate(component, bean);
String buff = TreeUtils.setupJSTreeCollectionComponent(validate);
writer.writeText(buff, null);
writer.endElement(XhtmlConstants.SCRIPT_ELEMENT);
}
}
@Override
protected String getDefaultStyleClass(FacesBean bean) {
return null;
}
protected boolean isImmediate(UIComponent component, FacesBean bean)
{
return Boolean.TRUE.equals(bean.getProperty(_immediateKey));
}
/**
* Render the navigation header bars, i.e. all the bars that appear above the
* actual data table including the breadcrumbs at the end.
*/
@Override
protected void renderNavigationHeaderBars(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component,
FacesBean bean) throws IOException
{
super.renderNavigationHeaderBars(context, arc, tContext, component, bean);
_renderBreadCrumbs(context, arc, tContext, component, bean);
}
@Override
protected final void renderRangePagingControl(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent table)
throws IOException
{
UIXHierarchy comp = (UIXHierarchy) table;
Object focusKey = comp.getFocusRowKey();
final Object oldKey = comp.getRowKey();
try
{
// set the collection that is being displayed:
comp.setRowKey(focusKey);
super.renderRangePagingControl(context, arc, tContext, table);
}
finally
{
comp.setRowKey(oldKey);
}
}
@Override
protected boolean hasControlBarLinks(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component) throws IOException
{
return
super.hasControlBarLinks(context, arc, tContext, component) ||
isExpandAllEnabled(component);
}
@Override
protected void renderControlBarLinks(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component,
boolean useDivider) throws IOException
{
boolean hasExpandAll = isExpandAllEnabled(component);
super.renderControlBarLinks(context, arc, tContext, component,
hasExpandAll || useDivider);
if (hasExpandAll && supportsScripting(arc)) //not in printable mode
{
// must render these IDs so that PPR can restore the focus correctly:
String preId = component.getClientId(context) + NamingContainer.SEPARATOR_CHAR;
TreeTableRenderingContext hContext = (TreeTableRenderingContext) tContext;
String onclick =
TreeUtils.callJSExpandAll(hContext.getUIXTreeTable(),
tContext.getJSVarName(),
true /*isExpand*/);
renderControlBarLink(context, arc, onclick, _EXPAND_ALL_TEXT_KEY,
arc.getIcon(SkinSelectors.AF_TREE_TABLE_EXPAND_ALL_ICON_NAME),
preId+"eAll", true);
onclick =
TreeUtils.callJSExpandAll(hContext.getUIXTreeTable(),
tContext.getJSVarName(),
false /*isExpand*/);
renderControlBarLink(context, arc, onclick, _COLLAPSE_ALL_TEXT_KEY,
arc.getIcon(SkinSelectors.AF_TREE_TABLE_COLLAPSE_ALL_ICON_NAME),
preId+"cAll", useDivider);
}
}
protected void renderControlBarLink(
FacesContext context,
RenderingContext arc,
String onclick,
String translationKey,
Icon icon,
String id,
boolean hasDivider
) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement("a", null);
writer.writeAttribute(XhtmlConstants.ID_ATTRIBUTE, id, null);
renderStyleClass(context, arc, SkinSelectors.NAV_BAR_ALINK_STYLE_CLASS);
writer.writeAttribute("onclick", onclick, null);
writer.writeURIAttribute("href", "#", null);
if (icon != null)
{
OutputUtils.renderIcon(context, arc, icon, arc.getTranslatedString(translationKey),
null);
} else
{
writer.writeText(arc.getTranslatedString(translationKey), null);
}
writer.endElement("a");
if (hasDivider)
writer.writeText(LINKS_DIVIDER_TEXT, null);
}
protected boolean isExpandAllEnabled(UIComponent component)
{
FacesBean bean = getFacesBean(component);
Object bool =
bean.getProperty(_expandAllEnabledKey);
if (bool == null)
bool = _expandAllEnabledKey.getDefault();
return Boolean.TRUE.equals(bool);
}
/**
* used to render special column headers, like select and details.
* @return the next physicalColumnIndex
*/
@Override
protected int renderSpecialColumns(
FacesContext context,
RenderingContext arc,
TableRenderingContext tContext,
UIComponent component,
int physicalColumnIndex)
throws IOException
{
// This renders a whole bunch of <TH>..</TH> elements or <TD>..</TD>
// elements depending on the RenderStage
physicalColumnIndex = super.renderSpecialColumns(context,
arc,
tContext,
component,
physicalColumnIndex);
final ColumnData colData = tContext.getColumnData();
TreeTableRenderingContext ttrc = (TreeTableRenderingContext) tContext;
if (ttrc.isFocusColumnVisible())
{
colData.setColumnIndex(physicalColumnIndex++,
ColumnData.SPECIAL_COLUMN_INDEX);
SpecialColumnRenderer focusRenderer = getFocusColumnRenderer();
UIComponent column = focusRenderer.getSpecialColumn();
delegateRenderer(context, arc, column,
getFacesBean(column), focusRenderer);
}
// render the object hierarchy column:
colData.setColumnIndex(physicalColumnIndex++,
ColumnData.SPECIAL_COLUMN_INDEX);
UIComponent treeNodeColumn = ttrc.getTreeNodeStamp();
delegateRenderer(context, arc, treeNodeColumn,
getFacesBean(treeNodeColumn), _TREE_NODE);
return physicalColumnIndex;
}
/**
* render all the table rows
*/
@Override
protected void renderTableRows(
FacesContext context,
RenderingContext arc,
TableRenderingContext trc,
UIComponent component,
FacesBean bean) throws IOException
{
TreeTableRenderingContext ttrc = (TreeTableRenderingContext) trc;
final RowData rowData = trc.getRowData();
boolean isEmptyTable = rowData.isEmptyTable();
// render all the table content rows
if (isEmptyTable)
_renderEmptyTableRow(context, arc, ttrc);
else
_renderTableRows(context, arc, ttrc, bean);
// render the footer
renderFooter(context, arc, trc, component);
// on an HGrid the number of rows depends on whether child nodes are
// expanded or not.
//rowData.setRowCount(rows);
}
protected String getControlLinkIconName(String translationKey)
{
if (translationKey == null)
return null;
return translationKey.equals(_SELECT_ALL_TEXT_KEY) ? SkinSelectors.AF_TREE_TABLE_SELECT_ALL_ICON_NAME
: SkinSelectors.AF_TREE_TABLE_SELECT_NONE_ICON_NAME;
}
protected SpecialColumnRenderer getFocusColumnRenderer()
{
return _FOCUS;
}
protected boolean isRootNodeRendered(FacesBean bean)
{
if (_rootNodeRendered == null)
return true;
return !Boolean.FALSE.equals(bean.getProperty(_rootNodeRendered));
}
//
// Private methods
//
// Renders the hGridLocator Icon
private void _renderLocatorIcon(
FacesContext fc,
RenderingContext arc)
throws IOException
{
Icon icon = arc.getIcon(SkinSelectors.AF_TREE_TABLE_LOCATOR_ICON_NAME);
if (icon != null)
{
Object altText = arc.getTranslatedString(_BREADCRUMBS_START_KEY);
// Use "middle" alignment to center the icon with text
Object align = OutputUtils.getMiddleIconAlignment(arc);
// We don't bother specifying the style class. This can
// be specified by the Icon itself, since we aren't rendering
// the Icon within a link (and thus we don't need to render
// the style class ourselves.)
OutputUtils.renderIcon(fc, arc, icon, altText, align);
}
}
private void _renderBreadCrumbs(
FacesContext fc,
RenderingContext arc,
TableRenderingContext context,
UIComponent tree,
FacesBean bean) throws IOException
{
// this renders <TR><TD><[crumbs]></TD></TR>
TreeTableRenderingContext hContext = (TreeTableRenderingContext) context;
// we will render the breadcrumbs only if they exist.
if (hContext.isFocusColumnVisible())
{
ResponseWriter writer = fc.getResponseWriter();
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
writer.startElement(XhtmlConstants.TABLE_DATA_ELEMENT, null);
writer.writeAttribute(XhtmlConstants.HEIGHT_ATTRIBUTE, "30", null);
final String borderStyleClass;
borderStyleClass = CellUtils.getBorderClass( true, //top
true, //left
false, //bottom
true); //right
renderStyleClasses(fc, arc, new String[] {
SkinSelectors.HGRID_LOCATOR_HEADER_STYLE,
borderStyleClass}
);
int colspan = context.getActualColumnCount();
writer.writeAttribute("colspan", IntegerUtils.getString(colspan), null);
// The locator icon hits the left border if not for this spacer
renderSpacer(fc, arc, "0", "2");
_renderLocatorIcon(fc, arc);
delegateRenderer(fc, arc, tree, bean, _CRUMBS);
writer.endElement(XhtmlConstants.TABLE_DATA_ELEMENT);
// end the <tr> containing the bread crumbs
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
}
}
@SuppressWarnings("unchecked")
private void _renderTableRows(
FacesContext context,
final RenderingContext arc,
final TreeTableRenderingContext ttrc,
final FacesBean bean)
throws IOException
{
final UIXTreeTable treeTableBase = ttrc.getUIXTreeTable();
final ResponseWriter writer = context.getResponseWriter();
final RowKeySet treeState = treeTableBase.getDisclosedRowKeys();
final int specialColCount = _getSpecialColCount(ttrc);
final boolean rootNodeRendered = isRootNodeRendered(bean);
TableUtils.RowLoop loop = new TableUtils.RowLoop()
{
@Override
protected void loop(FacesContext context, CollectionComponent treeTable)
throws IOException
{
Object focusPath = TreeUtils.getFocusRowKey(treeTableBase);
treeTableBase.setRowKey(focusPath);
processRow(context, treeTable);
}
@Override
protected void processRowImpl(FacesContext context, CollectionComponent treeTable)
throws IOException
{
if (rootNodeRendered || treeTableBase.getDepth() > 0)
{
writer.startElement(XhtmlConstants.TABLE_ROW_ELEMENT, null);
renderSingleRow(context, arc, ttrc, treeTableBase);
writer.endElement(XhtmlConstants.TABLE_ROW_ELEMENT);
}
//
// if (hasInvisibleNodes)
// {
// //context.addHiddenDataObject(treeTableBase.getRowData());
// }
if (treeTableBase.isContainer())
{
if (treeState.isContained() || (rootNodeRendered == false && treeTableBase.getDepth() == 0))
{
treeTableBase.enterContainer();
int rows = treeTableBase.getRows();
int rowCount = treeTableBase.getRowCount();
boolean renderNavRow = (rows > 0) &&
((rowCount < 0) || (rowCount > rows));
if (renderNavRow)
{
renderEmptyTableRow(context, arc, ttrc, specialColCount, _NAV_TOP);
}
super.loop(context, treeTable);
if (renderNavRow)
{
renderEmptyTableRow(context, arc, ttrc, specialColCount, _NAV_BOTTOM);
}
treeTableBase.exitContainer();
}
}
}
};
loop.run(context, treeTableBase);
//return currRow;
}
/**
* render a table row for empty tables. This blanks everything out and writes
* some text indicating to the user that the table is empty.
* @return zero (the number of rows in this table).
*/
private int _renderEmptyTableRow(
FacesContext context,
RenderingContext arc,
TreeTableRenderingContext ttrc) throws IOException
{
// we do not include the object hierarchy column in the specialCols
// count. This is because we want the emptyText to appear in the
// hierarchy column.
renderEmptyTableRow(context, arc, ttrc, _getSpecialColCount(ttrc));
return 0;
}
private int _getSpecialColCount(
TreeTableRenderingContext ttrc)
{
int specialCols = 0;
if (ttrc.hasSelection())
{
specialCols++;
}
if (ttrc.isFocusColumnVisible())
{
specialCols++;
}
return specialCols;
}
//
// Private variables
//
private static final CoreRenderer _NAV_TOP = new TreeTableNavRenderer(true);
private static final CoreRenderer _NAV_BOTTOM = new TreeTableNavRenderer(false);
private static final SpecialColumnRenderer _FOCUS = new FocusColumnRenderer();
private static final CoreRenderer _TREE_NODE = new TreeNodeColumnRenderer();
private static final CoreRenderer _CRUMBS = new BreadCrumbsRenderer();
private static final class BreadCrumbsRenderer extends org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.BreadCrumbsRenderer
{
@Override
protected void renderLink(
FacesContext context,
RenderingContext arc,
UIComponent child,
int renderedCount,
boolean isLastChild) throws IOException
{
TreeTableRenderingContext ttrc = TreeTableRenderingContext.getInstance();
UIXTreeTable tree = ttrc.getUIXTreeTable();
ResponseWriter out = context.getResponseWriter();
out.startElement("a", tree);
out.writeURIAttribute("href", "#" , null);
// put style classes on the links
if (isLastChild)
{
renderStyleClass(
context, arc,
SkinSelectors.AF_TREE_TABLE_MP_SELECTED_STYLE_CLASS);
}
else
{
renderStyleClass(
context, arc,
SkinSelectors.AF_TREE_TABLE_MP_STEP_STYLE_CLASS);
}
Object oldPath = tree.getRowKey();
Object focusRowKey = ttrc.getFocusRowKey();
List<Object> focusPath = tree.getAllAncestorContainerRowKeys(focusRowKey);
Object crumbPath = (renderedCount < focusPath.size())
? focusPath.get(renderedCount)
: focusRowKey;
try
{
tree.setRowKey(crumbPath);
String onclick =
TreeUtils.callJSFocusNode(tree, ttrc.getJSVarName());
out.writeAttribute("onclick", onclick , null);
super.renderLink(context, arc, child, renderedCount, isLastChild);
}
finally
{
tree.setRowKey(oldPath);
}
out.endElement("a");
}
@Override
protected boolean hasChildren(UIComponent component)
{
return false; // do not render the columns. only the pathStamp
}
@Override
protected boolean shouldRenderId(
FacesContext context,
UIComponent component)
{
// the ID is rendered by TreeTableRenderer and not org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.BreadCrumbsRenderer:
// bug 4587950:
return false;
}
@Override
protected UIComponent getStamp(
FacesContext context,
RenderingContext arc,
UIXHierarchy component,
FacesBean bean
)
{
UIComponent stamp = component.getFacet("pathStamp");
return stamp;
}
}
@Override
protected Map<String, String> createResourceKeyMap()
{
Map<String, String> tablemap = super.createResourceKeyMap();
Map<String, String> map =
ResourceKeyUtils.convertResourceKeyMap(tablemap, "table", "treeTable");
// we need a resource key map since we are using a navigationPath.
// and we are using table for the styles
map.put(SkinSelectors.AF_NAVIGATION_PATH_SEPARATOR_ICON_NAME,
SkinSelectors.AF_TREE_TABLE_MP_SEPARATOR_ICON_NAME);
map.put(SkinSelectors.AF_NAVIGATION_PATH_STYLE_CLASS,
SkinSelectors.AF_TREE_TABLE_MP_STYLE_CLASS);
map.put(SkinSelectors.AF_TABLE_CONTENT_STYLE,
SkinSelectors.AF_TREE_TABLE_CONTENT_STYLE);
map.put(SkinSelectors.AF_TABLE_SUB_CONTROL_BAR_STYLE,
SkinSelectors.AF_TREE_TABLE_SUB_CONTROL_BAR_STYLE);
map.put(SkinSelectors.AF_TABLE_CONTROL_BAR_TOP_STYLE,
SkinSelectors.AF_TREE_TABLE_CONTROL_BAR_TOP_STYLE);
map.put(SkinSelectors.AF_TABLE_CONTROL_BAR_BOTTOM_STYLE,
SkinSelectors.AF_TREE_TABLE_CONTROL_BAR_BOTTOM_STYLE);
return Collections.unmodifiableMap(map);
}
// translation keys
private static final String _EXPAND_ALL_TEXT_KEY =
"af_treeTable.EXPAND_ALL";
private static final String _COLLAPSE_ALL_TEXT_KEY =
"af_treeTable.COLLAPSE_ALL";
private static final String _BREADCRUMBS_START_KEY =
"af_treeTable.BREADCRUMB_START_TIP";
private PropertyKey _immediateKey;
private PropertyKey _expandAllEnabledKey;
private PropertyKey _rootNodeRendered;
private static final Object _JS_LIBS_KEY = new Object();
private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(TreeTableRenderer.class);
}