/*
* 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.util.List;
import java.util.Map;
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.UIXShowDetail;
import org.apache.myfaces.trinidad.component.core.layout.CorePanelAccordion;
import org.apache.myfaces.trinidad.component.core.layout.CoreShowDetailItem;
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.event.DisclosureEvent;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidadinternal.agent.TrinidadAgent;
/**
* Renderer for PanelAccordion
*
* @version $Name: $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/renderkit/html/layout/CoreShowOneAccordionRenderer.java#0 $) $Date: 10-nov-2005.19:01:13 $
*/
public class PanelAccordionRenderer extends XhtmlRenderer
{
public PanelAccordionRenderer()
{
super(CorePanelAccordion.TYPE);
}
@Override
protected void findTypeConstants(FacesBean.Type type)
{
super.findTypeConstants(type);
_discloseNoneKey = type.findKey("discloseNone");
_discloseManyKey = type.findKey("discloseMany");
}
@SuppressWarnings("unchecked")
@Override
public void decode(FacesContext context, UIComponent component)
{
Map<String, String> parameters =
context.getExternalContext().getRequestParameterMap();
Object event = parameters.get(XhtmlConstants.EVENT_PARAM);
if (XhtmlConstants.HIDE_EVENT.equals(event) ||
XhtmlConstants.SHOW_EVENT.equals(event))
{
Object source = parameters.get(XhtmlConstants.SOURCE_PARAM);
String id = component.getClientId(context);
if (id.equals(source))
{
boolean isDisclosed = XhtmlConstants.SHOW_EVENT.equals(event);
String itemId = parameters.get("targetItem");
if (itemId != null)
{
List<UIComponent> children = component.getChildren();
for (UIComponent child : children)
{
if (!(child instanceof UIXShowDetail))
continue;
// Don't even bother with disabled showDetailItems -
// a malicious user should not be able to force
// a disabled item open by dummying up an event
if (!child.isRendered() || _isItemDisabled(child))
continue;
if (itemId.equals(child.getClientId(context)))
{
(new DisclosureEvent(child, isDisclosed)).queue();
RequestContext rc = RequestContext.getCurrentInstance();
// Don't force PPR on for the browsers that do not support Ajax
Object cap = rc.getAgent().getCapabilities().get(TrinidadAgent.CAP_PARTIAL_RENDERING);
if ((cap != null) && (Boolean.TRUE.equals(cap)))
{
RequestContext.getCurrentInstance().addPartialTarget(component);
PartialPageUtils.forcePartialRendering(context);
}
break;
}
}
}
}
}
}
@Override
public boolean getRendersChildren()
{
return true;
}
@Override
protected boolean shouldRenderId(
FacesContext context,
UIComponent component)
{
return true;
}
/**
* First: If nothing is disclosed, makes the first child disclosed.
*
* Makes sure that the child being disclosed has rendered = true
* and is not disabled.
*
* Second: Renders a vertical panel bar and children in individual panels.
*
* For the panel bar, draws a DIV that forms the outline of panels.
* Within the DIV, iteratively calls the encodeBegin, encodeChildren and
* encodeEnd on the panel children (if they have rendered and disclosed
* set to true).
*
* Non UIXShowDetail children are ignored.
* The title of each of the panels is the same as the text assigned to
* UIXShowDetail child. When text attribute is not specified,
* title remains blank.
* @param context the faces context object
* @param component the UIComponent object
* @throws IOException when some issues while writing output
*/
@SuppressWarnings("unchecked")
@Override
protected void encodeAll(
FacesContext context,
RenderingContext arc,
UIComponent component,
FacesBean bean) throws IOException
{
FormData fData = arc.getFormData();
String formName = "";
if (fData != null)
{
formName = fData.getName();
if (formName == null)
{
_LOG.warning("PANELACCORDION_MUST_INSIDE_FORM");
return;
}
// Hidden field to store parameter targetItem is needed for non
// Ajax browsers to pass the target item Id back to the server.
boolean pprEnabled =
PartialPageUtils.supportsPartialRendering(arc);
if (!pprEnabled)
{
fData.addNeededValue(XhtmlConstants.TARGETITEM_PARAM);
}
}
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
if (_isItemDisabled(detailChild))
{
continue;
}
if (renderableChild == null)
{
renderableChild = detailChild;
}
if (detailChild.isDisclosed())
{
disclosedChild = detailChild;
// A diclosed child found. return.
break;
}
}
}
// If we have a minimum of 1 disclosed child and none have been disclosed
// yet, disclose the first rendered one:
if ( (disclosedChild == null) && !getDiscloseNone(bean) &&
(renderableChild != null) && !renderableChild.isDisclosedTransient())
{
renderableChild.setDisclosed(true);
}
ResponseWriter out = context.getResponseWriter();
String compId = component.getClientId(context);
out.startElement("div", component);
renderId(context, component);
renderAllAttributes(context, arc, bean);
boolean discloseMany = getDiscloseMany(bean);
boolean discloseNone = getDiscloseNone(bean);
boolean disclosedFixed = false;
if (discloseMany && !discloseNone) // must keep at least one item disclosed
{
// This is a special case where we must determine if we have to fix the
// disclosure state of one of the items.
int disclosedCount = 0;
for (UIComponent child : (List<UIComponent>) component.getChildren())
{
if (!(child instanceof UIXShowDetail) ||
!child.isRendered())
continue;
UIXShowDetail detailItem = (UIXShowDetail) child;
if (detailItem.isDisclosed())
{
disclosedCount++;
if (disclosedCount > 1)
{
break; // we have enough information at this point to stop counting
}
}
}
if (disclosedCount <= 1)
{
disclosedFixed = true;
}
}
boolean childAlreadyRendered = false;
for (UIComponent child : (List<UIComponent>) component.getChildren())
{
if (!(child instanceof UIXShowDetail) ||
!child.isRendered())
continue;
UIXShowDetail detailItem = (UIXShowDetail) child;
boolean disabled = _isItemDisabled(detailItem);
String titleText = (String)
detailItem.getAttributes().get(CoreShowDetailItem.TEXT_KEY.getName());
boolean disclosed = detailItem.isDisclosed();
if (childAlreadyRendered)
{
// The detail child should be disclosed only when all three criteria met
// 1. is marked as disclosed
// 2. is not disabled and
// 3. if a child is not already disclosed. This occurs when more than
// one showDetail child has it's disclosed property set to true.
disclosed = false;
}
// Header renderer section.
out.startElement("div", detailItem);
String detailItemId = detailItem.getClientId(context);
String itemStyleClass;
if (disabled)
itemStyleClass = getHeaderDisabledStyleClass();
else if (disclosed)
itemStyleClass = getHeaderExpandedStyleClass();
else
itemStyleClass = getHeaderCollapsedStyleClass();
renderStyleClass(context, arc, itemStyleClass);
// Render the toolbar component, if any (we use float to keep
// the toolbar on the right - or left, in RTL languages - so
// it has to be rendered first)
UIComponent toolbar = getFacet(detailItem,
CoreShowDetailItem.TOOLBAR_FACET);
if (toolbar != null)
{
out.startElement("div", detailItem);
renderStyleClass(context, arc, SkinSelectors.AF_PANELACCORDION_TOOLBAR_STYLE_CLASS);
encodeChild(context, toolbar);
out.endElement("div");
}
boolean javaScriptSupport = supportsScripting(arc);
if (javaScriptSupport)
{
out.startElement("a", null);
out.writeAttribute("name", detailItemId, 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.
out.startElement("input", null);
out.writeAttribute("type", "submit", null);
}
renderStyleClass(context, arc,
disabled
? getLinkDisabledStyleClass()
: getLinkEnabledStyleClass());
// If the child is disclosable and enabled...
boolean disclosable =
discloseNone || (! disclosed) || (discloseMany && !disclosedFixed);
if ( disclosable && (! disabled) )
{
boolean isImmediate = detailItem.isImmediate();
String event = disclosed ? "hide" : "show";
if (javaScriptSupport)
{
String onClickHandler = _getFormSubmitScript(component,
arc,
event,
detailItemId,
formName,
compId,
isImmediate);
out.writeAttribute("onclick", onClickHandler, null);
out.writeAttribute("href", "#", null);
}
else
{
String nameAttri = XhtmlUtils.getEncodedParameter
(XhtmlConstants.SOURCE_PARAM)
+ XhtmlUtils.getEncodedParameter(compId)
+ XhtmlUtils.getEncodedParameter
(XhtmlConstants.EVENT_PARAM)
+ XhtmlUtils.getEncodedParameter(event)
+ XhtmlUtils.getEncodedParameter
(XhtmlConstants.TARGETITEM_PARAM)
+ detailItemId;
out.writeAttribute("name", nameAttri, null);
}
}
if (javaScriptSupport)
{
// =-=rbaranwa Per the UI Review, no icon to be rendered when
// panel is disabled.
if (! disabled)
{
ShowDetailRenderer.renderDisclosureIcon(context,
arc,
disclosed,
getDisclosedTipKey(),
getUndisclosedTipKey());
}
if (titleText != null)
{
out.writeText(titleText, null);
}
out.endElement("a");
}
else
{
// Since we cannot render any image element as a child of input element,
// just render the icon symbol along with the text.
String icon = disclosed ? XhtmlConstants.NON_JS_DETAIL_DISCLOSED_ICON :
XhtmlConstants.NON_JS_DETAIL_UNDISCLOSED_ICON;
if (titleText != null)
{
icon = icon + titleText;
}
out.writeAttribute("value", icon, null);
if (disabled)
{
out.writeAttribute("disabled", Boolean.TRUE, "disabled");
}
out.endElement("input");
}
out.endElement("div"); // Ending div for an individual panel
// The detail child should be disclosed only when all three criteria met
// 1. is marked as disclosed
// 2. is not disabled and
// 3. if a child is not already disclosed. This occurs when more than
// one showDetail child has it's disclosed property set to true.
if (disclosed && (! disabled) && (! childAlreadyRendered) )
{
_encodeDetailItem(context, arc, component, detailItem, out);
if (!discloseMany)
{
childAlreadyRendered = true;
}
}
}
out.endElement("div");
}
@Override
protected String getDefaultStyleClass(FacesBean bean)
{
return SkinSelectors.AF_PANELACCORDION_STYLE_CLASS;
}
protected String getContentStyleClass()
{
return SkinSelectors.AF_PANELACCORDION_CONTENT_STYLE_CLASS;
}
protected String getHeaderDisabledStyleClass()
{
return SkinSelectors.AF_PANELACCORDION_HEADER_DISABLED_STYLE_CLASS;
}
protected String getHeaderExpandedStyleClass()
{
return SkinSelectors.AF_PANELACCORDION_HEADER_EXPANDED_STYLE_CLASS;
}
protected String getHeaderCollapsedStyleClass()
{
return SkinSelectors.AF_PANELACCORDION_HEADER_COLLAPSED_STYLE_CLASS;
}
protected String getLinkDisabledStyleClass()
{
return SkinSelectors.AF_PANELACCORDION_TITLE_LINK_DISABLED_STYLE_CLASS;
}
protected String getLinkEnabledStyleClass()
{
return SkinSelectors.AF_PANELACCORDION_TITLE_LINK_STYLE_CLASS;
}
protected String getDisclosedTipKey()
{
return _DISCLOSED_TIP_KEY;
}
protected String getUndisclosedTipKey()
{
return _UNDISCLOSED_TIP_KEY;
}
/**
* Encodes the disclosed child.
*
* Generates the required markup for the disclosed child.
*
* @param context the faces context object
* @param component the UIComponent object
* @param detailItem the UIXShowDetailItem thats disclosed
* @param out the response writer object
* @throws IOException when some issues while writing output
*/
private void _encodeDetailItem(FacesContext context,
RenderingContext arc,
UIComponent component,
UIXShowDetail detailItem,
ResponseWriter out)
throws IOException
{
out.startElement("table", component);
out.writeAttribute("cellSpacing", "0", null);
out.writeAttribute("cellPadding", "0", null);
out.writeAttribute("summary", "", null);
renderStyleClass(context, arc, getContentStyleClass());
out.startElement("tr", component);
out.startElement("td", component);
encodeChild(context, detailItem);
out.endElement("td");
out.endElement("tr");
out.endElement("table"); // Ending table for the contained child
}
/**
* Creates javascript used to submit the page.
*/
private String _getFormSubmitScript(UIComponent component,
RenderingContext arc,
String event,
String detailItemId,
String formName,
String compId,
boolean isImmediate)
{
// Check if PPR enabled, do a _submitPartialChange, else do a formSubmit.
String onClickHandler = "";
boolean pprEnabled =
PartialPageUtils.supportsPartialRendering(arc);
String validate = "1";
if (isImmediate)
{
validate = "0";
}
if (pprEnabled)
{
StringBuilder onClickHandlerBuff =
new StringBuilder("_submitPartialChange('")
.append(formName)
.append("',")
.append(validate)
.append(", {event:'")
.append(event)
.append("',source:'")
.append(compId)
.append("',targetItem:'")
.append(detailItemId)
.append("'});return false;");
onClickHandler = onClickHandlerBuff.toString();
}
else
{
StringBuilder onClickHandlerBuff = new StringBuilder("submitForm('")
.append(formName)
.append("',")
.append(validate)
.append(", {event:'")
.append(event)
.append("',source:'")
.append(compId)
.append("',targetItem:'")
.append(detailItemId)
.append("'});return false;");
onClickHandler = onClickHandlerBuff.toString();
}
return onClickHandler;
}
protected boolean getDiscloseMany(FacesBean bean)
{
Object o = bean.getProperty(_discloseManyKey);
if (o == null)
o = _discloseManyKey.getDefault();
return Boolean.TRUE.equals(o);
}
protected boolean getDiscloseNone(FacesBean bean)
{
Object o = bean.getProperty(_discloseNoneKey);
if (o == null)
o = _discloseNoneKey.getDefault();
return Boolean.TRUE.equals(o);
}
private boolean _isItemDisabled(UIComponent component)
{
Object isDisabled = component.getAttributes().get(
CoreShowDetailItem.DISABLED_KEY.getName());
return Boolean.TRUE.equals(isDisabled);
}
private static final TrinidadLogger _LOG =
TrinidadLogger.createTrinidadLogger(PanelAccordionRenderer.class);
private PropertyKey _discloseNoneKey;
private PropertyKey _discloseManyKey;
private static final String _DISCLOSED_TIP_KEY =
"af_panelAccordion.DISCLOSED_TIP";
private static final String _UNDISCLOSED_TIP_KEY =
"af_panelAccordion.UNDISCLOSED_TIP";
}