/*
* 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.html.layout;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.apache.myfaces.trinidad.component.UIXShowDetail;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.render.RenderUtils;
import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidad.context.Agent;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.SkinSelectors;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.XhtmlRenderer;
import org.apache.myfaces.trinidadinternal.ui.UIConstants;
import org.apache.myfaces.trinidadinternal.ui.UIXRenderingContext;
import org.apache.myfaces.trinidadinternal.ui.laf.base.xhtml.XhtmlLafRenderer;
import org.apache.myfaces.trinidadinternal.uinode.UINodeRendererBase;
/**
* Base Renderer for ShowOneChoice and PanelRadio
* <p>
* @version $Name: $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/renderkit/html/layout/ShowOneListRendererBase.java#0 $) $Date: 10-nov-2005.19:01:14 $
*/
abstract class ShowOneListRendererBase extends UINodeRendererBase
{
/**
* {@inheritDoc}
* @return true always
*/
@Override
public boolean getRendersChildren()
{
return true;
}
/**
* If nothing is disclosed, makes the first child disclosed.
*
* Makes sure that the child it's disclosing has rendered = true
* and is not disabled.
*
* @param context
* @param component
* @throws IOException
*/
@SuppressWarnings("unchecked")
@Override
public void encodeBegin(FacesContext context, UIComponent component)
throws IOException
{
_LOG.entering("ShowOneListRendererBase", "encodeBegin");
List<UIComponent> children = component.getChildren();
int numChildren = children.size();
UIComponent disclosedChild = null;
UIXShowDetail renderableChild = null;
for (int indxChild = 0; indxChild < numChildren ; indxChild++ )
{
UIComponent child = children.get(indxChild);
if (! (child instanceof UIXShowDetail) )
{
continue;
}
UIXShowDetail detailChild = (UIXShowDetail) children.get(indxChild);
if (detailChild.isRendered())
{
// Mark the first renderable child
Object disabled =
detailChild.getAttributes().get(
UIConstants.DISABLED_ATTR.getAttributeName());
if (Boolean.TRUE.equals(disabled))
{
continue;
}
if (renderableChild == null)
{
renderableChild = detailChild;
}
if (detailChild.isDisclosed())
{
disclosedChild = detailChild;
}
}
}
// If nothing has been disclosed as of yet, disclose the first rendered one
if ( (disclosedChild == null) && (renderableChild != null) &&
!renderableChild.isDisclosedTransient())
{
renderableChild.setDisclosed(true);
}
_LOG.exiting("ShowOneListRendererBase", "encodeBegin");
}
/**
* Renders a table and renders a choice list / radio button within it.
*
* Depending on position and alignment attributes, renders the list control
* and disclosed showDetail child in the correct row and column.
*
* Non UIXShowDetail children are ignored.
* The label of each of radio / choice items is the same as the text assigned
* to CoreShowDetailItem child. If no text attrbute value specified,
* the label remains blank.
*/
@Override
public void encodeChildren(FacesContext context, UIComponent component)
throws IOException
{
writeAdditionalJS(context, component); // To spit out additional JS if any
String alignment = _getAlignment(component);
if (alignment == null)
{
alignment = _ALIGNMENT_DEFAULT_VALUE;
}
// No need to check for invalid value of alignment attr value as
// it's handled below.
// If the position is either null or an invalid value, default it.
String position = _getPosition(component);
if (! positionMap.containsKey(position))
{
position = _POSITION_DEFAULT_VALUE;
}
_LOG.finest("ShowOneListRendererBase.encodeChildren: alignment: {0} , " +
"position: {1} ", new Object[]{alignment, position} );
ResponseWriter out = context.getResponseWriter();
String disclosedItemId = _getDisclosedItemId(context, component);
RenderingContext arc = RenderingContext.getCurrentInstance();
boolean isDesktop = (arc.getAgent().getType().equals(Agent.TYPE_DESKTOP));
// Wrap the table element with a div tag for mobile browsers since some of it
// doesn't support PPR for table element.
if(!isDesktop )
{
out.startElement("div", component);
out.writeAttribute("id", component.getClientId(context), null);
}
out.startElement("table", component);
if(isDesktop )
{
out.writeAttribute("id", component.getClientId(context), null);
}
String shortDesc = (String) component.getAttributes().get("shortDesc");
if (shortDesc != null)
{
out.writeAttribute("summary", shortDesc, null);
}
else
{
out.writeAttribute("summary", "", null);
}
out.writeAttribute("border", "0", null);
out.writeAttribute("cellspacing", "0", null);
out.writeAttribute("cellpadding", "0", null);
String styleClass = (String) component.getAttributes().get("styleClass");
if (styleClass != null)
{
XhtmlRenderer.renderStyleClass(context, arc, styleClass);
}
UIXRenderingContext rCtx = getRenderingContext(context, component);
ShowOneUtils.renderGenericAttributes(rCtx, component, out);
out.startElement("tr", component);
// =-= rbaranwa push some of the below into functions
if (position.equals(_POSITION_LEFT))
{
out.startElement("td", component);
_renderAlignmentTopBottom(out, alignment);
renderListDisplay(context, component, disclosedItemId);
out.endElement("td");
renderSpacerTD(out, component, _SEPARATOR_SIZE);
out.startElement("td", component);
out.writeAttribute("align", "left", null);
_findAndEncodeChild(context, component, disclosedItemId);
out.endElement("td");
}
if (position.equals(_POSITION_RIGHT))
{
out.startElement("td", component);
out.writeAttribute("align", "right", null);
_findAndEncodeChild(context, component, disclosedItemId);
out.endElement("td");
renderSpacerTD(out, component, _SEPARATOR_SIZE);
out.startElement("td", component);
_renderAlignmentTopBottom(out, alignment);
out.writeAttribute("align", "left", null);
renderListDisplay(context, component, disclosedItemId);
out.endElement("td");
}
if (position.equals(_POSITION_BOTTOM))
{
out.startElement("td", component);
_findAndEncodeChild(context, component, disclosedItemId);
out.endElement("td");
out.endElement("tr");
_renderSpacerTR(out, component);
out.startElement("tr", component);
out.startElement("td", component);
_renderAlignmentLeftRight(out, alignment);
renderListDisplay(context, component, disclosedItemId);
out.endElement("td");
}
if (position.equals(_POSITION_TOP))
{
out.startElement("td", component);
_renderAlignmentLeftRight(out, alignment);
renderListDisplay(context, component, disclosedItemId);
out.endElement("td");
out.endElement("tr");
_renderSpacerTR(out, component);
out.startElement("tr", component);
out.startElement("td", component);
_findAndEncodeChild(context, component, disclosedItemId);
out.endElement("td");
}
out.endElement("tr");
out.endElement("table");
if(!isDesktop )
{
out.endElement("div");
}
}
/**
* Dummy method to prevent base class' encodeEnd call.
*
* @param context the faces context object
* @param component the UIComponent object
* @throws IOException when some issues while writing output
*/
@Override
public void encodeEnd(FacesContext context,
UIComponent component)
throws IOException
{
// This is done so that the UIXComponentUINode does not
// go looking for a UIX 22 style renderer for this component
}
/**
* Writes any additional javascriipt required to support the control.
*
* Default implementation doesn't do anything.
*
* @param context the facesContext object
* @param component
* @throws IOException when any IO error while writing markup
*/
protected void writeAdditionalJS(FacesContext context,
UIComponent component)
throws IOException
{
// Default implementation writes no javascript.
}
/**
* Returns true if the showOneChoice element has an id
* and the browser supports PPR
*/
protected boolean elementSupportsPartial(UIXRenderingContext context,
String id)
{
return ((id != null) &&
XhtmlLafRenderer.supportsPartialRendering(context));
}
/**
* Renders the label associated with the control.
*
* Takes care of encoding for label text and the accessKey.
*
*/
protected void renderSelectLabel(UIXRenderingContext rCtx,
UIComponent component,
ResponseWriter out,
String compId)
throws IOException
{
out.startElement("td", component);
out.writeAttribute("align", "left", null);
out.writeAttribute("nowrap", Boolean.TRUE, null);
out.startElement("span", component);
XhtmlLafRenderer.renderStyleClassAttribute(rCtx, getLabelClassName());
out.startElement("label", component);
String controlId = getHTMLControlID(compId);
out.writeAttribute("for", controlId, null);
Character accessChar =
(Character) component.getAttributes().get("accessKey");
if (accessChar != null)
{
out.writeAttribute("accessKey", accessChar.toString(), null);
}
writeLabel(out,
component,
(String) component.getAttributes().get("label"));
out.endElement("label");
out.endElement("span");
out.endElement("td");
}
/**
* Renders the label text with character underlined for label.
*
* Takes care of encoding for label text and the accessKey.
*
*/
protected void writeLabel(ResponseWriter out,
UIComponent component,
String label)
throws IOException
{
// AdamWiner: TODO: replace this with a call to AccessKeyUtils
Character accessChar =
(Character) component.getAttributes().get("accessKey");
if (label != null)
{
if (accessChar == null)
{
out.writeText(label, null);
}
else
{
int accessKeyIndex = label.indexOf(accessChar.charValue());
if (accessKeyIndex < 0)
{
out.writeText(label, null);
}
else
{
// String stripping to remove & will already be done in Tag impl
String strBefAccessKey = label.substring(0, accessKeyIndex);
String strAfterAccessKey = label.substring(accessKeyIndex + 1,
label.length());
out.writeText(strBefAccessKey, null);
//ADFFACES-153: use default style (underline) for access key
out.startElement ("span", null);
XhtmlRenderer.renderStyleClass (FacesContext.getCurrentInstance(),
RenderingContext.getCurrentInstance(),
SkinSelectors.AF_ACCESSKEY_STYLE_CLASS);
out.writeText (accessChar.toString(), null);
out.endElement ("span");
out.writeText(strAfterAccessKey, null);
}
}
}
}
/**
* Generates markup for rendering a blank TD.
*
*/
protected void renderSpacerTD(ResponseWriter out,
UIComponent component,
String separatorSize)
throws IOException
{
out.startElement("td", component);
out.writeAttribute("width", separatorSize, null);
out.endElement("td");
}
/**
* Gets id of the rendered HTML control
*/
protected String getHTMLControlID(String compId)
{
// Default implementation just returns the same id.
// Choice implementation returns id of the select HTML control
return compId;
}
/**
* Gets classname of the text associated with HTML control text
*/
protected String getFieldTextClass()
{
return SkinSelectors.AF_FIELD_TEXT_STYLE_CLASS;
}
/**
* Gets classname of the text associated with HTML control text
*/
protected String getLabelControlSeparatorSize()
{
return _LABEL_CONTROL_SEPARATOR_SIZE;
}
/**
* Renders the list UI Control either radio buttons or choice.
*
*/
protected abstract void renderListDisplay(FacesContext context,
UIComponent component,
String disclosedChildId)
throws IOException;
/**
* Gets class name to be used for controls' label.
*
*/
private String getLabelClassName()
{
return SkinSelectors.AF_LABEL_TEXT_STYLE_CLASS;
}
/**
* Returns first disclosed child's Id.
*
* @param context the facesContext object
* @param component
* @return String id of the first showDetailItem child that can be rendered
* and is disclosed
* @throws IOException
*/
@SuppressWarnings("unchecked")
private static String _getDisclosedItemId(FacesContext context,
UIComponent component)
throws IOException
{
String returnId = null;
ListIterator<UIComponent> iter = component.getChildren().listIterator();
if (iter == null)
{
return null;
}
// This will only render UIXShowDetail children.
// other children are ignored.
while ( iter.hasNext())
{
UIComponent child = iter.next();
if (! child.isRendered() )
{
continue;
}
// only CoreShowDetailItem children are rendered. Rest are ignored.
if ( (child instanceof UIXShowDetail))
{
UIXShowDetail detailChild = (UIXShowDetail) child;
if (detailChild.isDisclosed())
{
returnId = detailChild.getClientId(context);
break;
}
}
}
return returnId;
}
/**
* Get attribute value for position attribute.
*
* @param component
* @return value contained in position attribute in component's attr map
*/
private static String _getPosition(UIComponent component)
{
return (String) component.getAttributes().get("position");
}
/**
* Get attribute value for alignment attribute.
*
* @param component
* @return value contained in alignment attribute in component's attr map
*/
private static String _getAlignment(UIComponent component)
{
return (String) component.getAttributes().get("alignment");
}
/**
* Find and encodes recursivly the child specified.
*
* If the specified child can't be found, doesn't do anything.
*
* @param context the facesContext object
* @param parent which contains the child to be encoded
* @param disclosedChildId id of the child which occurs in parent
* and has to be encoded recursively.
*
* @throws IOException when any IO error while writing markup
*/
@SuppressWarnings("unchecked")
private static void _findAndEncodeChild(FacesContext context,
UIComponent parent,
String disclosedChildId)
throws IOException
{
UIComponent disclosedChild = _findChild(context,
parent,
disclosedChildId);
if (disclosedChild == null)
{
return;
}
// Paint container span for showDetail child
ResponseWriter out = context.getResponseWriter();
out.startElement("span", disclosedChild);
// Render the children of disclosedChild since the child is disclosed.
if (disclosedChild.isRendered())
{
if (disclosedChild.getChildCount() > 0)
{
for(UIComponent child : (List<UIComponent>)disclosedChild.getChildren())
{
RenderUtils.encodeRecursive(context, child);
}
}
}
out.endElement("span");
// end container span for showDetail child
}
/**
* Find the child whose Id is passed as argument.
*
* If the specified child can't be found, doesn't do anything.
*
* @param context the facesContext object
* @param parent which may contain disclosedChildId as it's child
* @param disclosedChildId id of the child to be found among it's children
*
*/
@SuppressWarnings("unchecked")
private static UIComponent _findChild(FacesContext context,
UIComponent component,
String disclosedChildId)
{
if (disclosedChildId == null)
{
return null;
}
for(UIComponent child : (List<UIComponent>)component.getChildren())
{
String childId = child.getClientId(context);
if (disclosedChildId.equals(childId))
{
return child;
}
}
return null;
}
/**
* Generates markup for rendering a blank TR and TD within it.
*
*/
private void _renderSpacerTR(ResponseWriter out, UIComponent component)
throws IOException
{
out.startElement("tr", component);
out.startElement("td", component);
out.writeAttribute("height", _SEPARATOR_SIZE, null);
out.endElement("td");
out.endElement("tr");
}
/**
* Generates markup for rendering aligning along top/bottom.
*
*/
private void _renderAlignmentTopBottom(ResponseWriter out, String alignment)
throws IOException
{
if (alignment.equals(_ALIGNMENT_TOP))
{
out.writeAttribute("valign", "top", null);
}
else if (alignment.equals(_ALIGNMENT_BOTTOM))
{
out.writeAttribute("valign", "bottom", null);
}
else // is an invalid combination now
{
out.writeAttribute("valign", "middle", null);
}
}
/**
* Generates markup for rendering aligning along start(left)/end(right).
*
*/
private void _renderAlignmentLeftRight(ResponseWriter out, String alignment)
throws IOException
{
if (alignment.equals(_ALIGNMENT_LEFT))
{
out.writeAttribute("align", "left", null);
}
else if (alignment.equals(_ALIGNMENT_RIGHT))
{
out.writeAttribute("align", "right", null);
}
else // is an invalid combination now
{
out.writeAttribute("align", "center", null);
}
}
// ShowOne alignment related constants
private static final String _ALIGNMENT_LEFT = "start";
private static final String _ALIGNMENT_RIGHT = "end";
private static final String _ALIGNMENT_TOP = "top";
private static final String _ALIGNMENT_BOTTOM = "bottom";
private static final String _ALIGNMENT_CENTER = "center";
private static final String _ALIGNMENT_DEFAULT_VALUE = _ALIGNMENT_CENTER;
// ShowOne position related constants
private static final String _POSITION_LEFT = "start";
private static final String _POSITION_RIGHT = "end";
private static final String _POSITION_TOP = "top";
private static final String _POSITION_BOTTOM = "bottom";
private static final String _POSITION_DEFAULT_VALUE = _POSITION_LEFT;
private static Map<String, String> positionMap;
static
{
positionMap = new HashMap<String, String>(4);
positionMap.put(_POSITION_LEFT, _POSITION_LEFT);
positionMap.put(_POSITION_RIGHT, _POSITION_RIGHT);
positionMap.put(_POSITION_TOP, _POSITION_TOP);
positionMap.put(_POSITION_BOTTOM, _POSITION_BOTTOM);
}
private static final String _SEPARATOR_SIZE = "8";
private static final String _LABEL_CONTROL_SEPARATOR_SIZE = "12";
private static final TrinidadLogger _LOG =
TrinidadLogger.createTrinidadLogger(ShowOneListRendererBase.class);
}