/*
* Copyright 2004 The Apache Software Foundation.
*
* Licensed 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.renderkit.html;
import org.apache.myfaces.config.MyfacesConfig;
import org.apache.myfaces.renderkit.JSFAttr;
import org.apache.myfaces.renderkit.RendererUtils;
import org.apache.myfaces.renderkit.html.util.DummyFormResponseWriter;
import org.apache.myfaces.renderkit.html.util.DummyFormUtils;
import org.apache.myfaces.renderkit.html.util.JavascriptUtils;
import javax.faces.application.StateManager;
import javax.faces.application.ViewHandler;
import javax.faces.component.*;
import javax.faces.component.html.HtmlCommandLink;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.event.ActionEvent;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Iterator;
/**
* @author Manfred Geiler
* @version $Revision: 291056 $ $Date: 2005-09-22 21:34:02 -0400 (Thu, 22 Sep 2005) $
*/
public abstract class HtmlLinkRendererBase
extends HtmlRenderer
{
public static final String URL_STATE_MARKER = "JSF_URL_STATE_MARKER=DUMMY";
public static final int URL_STATE_MARKER_LEN = URL_STATE_MARKER.length();
//private static final Log log = LogFactory.getLog(HtmlLinkRenderer.class);
public boolean getRendersChildren()
{
// We must be able to render the children without a surrounding anchor
// if the Link is disabled
return true;
}
public void decode(FacesContext facesContext, UIComponent component)
{
super.decode(facesContext, component); //check for NP
if (component instanceof UICommand)
{
String clientId = component.getClientId(facesContext);
String reqValue = (String)facesContext.getExternalContext().getRequestParameterMap().get(HtmlRendererUtils.getHiddenCommandLinkFieldName(HtmlRendererUtils.getFormName(component, facesContext)));
if (reqValue != null && reqValue.equals(clientId))
{
component.queueEvent(new ActionEvent(component));
}
}
else if (component instanceof UIOutput)
{
//do nothing
}
else
{
throw new IllegalArgumentException("Unsupported component class " + component.getClass().getName());
}
}
public void encodeBegin(FacesContext facesContext, UIComponent component) throws IOException
{
super.encodeBegin(facesContext, component); //check for NP
if (component instanceof UICommand)
{
renderCommandLinkStart(facesContext, component,
component.getClientId(facesContext),
((UICommand)component).getValue(),
getStyle(facesContext, component),
getStyleClass(facesContext, component));
}
else if (component instanceof UIOutput)
{
renderOutputLinkStart(facesContext, (UIOutput)component);
}
else
{
throw new IllegalArgumentException("Unsupported component class " + component.getClass().getName());
}
}
/**
* Can be overwritten by derived classes to overrule the style to be used.
*/
protected String getStyle(FacesContext facesContext, UIComponent link)
{
if (link instanceof HtmlCommandLink)
{
return ((HtmlCommandLink)link).getStyle();
}
else
{
return (String)link.getAttributes().get(HTML.STYLE_ATTR);
}
}
/**
* Can be overwritten by derived classes to overrule the style class to be used.
*/
protected String getStyleClass(FacesContext facesContext, UIComponent link)
{
if (link instanceof HtmlCommandLink)
{
return ((HtmlCommandLink)link).getStyleClass();
}
else
{
return (String)link.getAttributes().get(HTML.STYLE_CLASS_ATTR);
}
}
public void encodeChildren(FacesContext facesContext, UIComponent component) throws IOException
{
RendererUtils.renderChildren(facesContext, component);
}
public void encodeEnd(FacesContext facesContext, UIComponent component) throws IOException
{
super.encodeEnd(facesContext, component); //check for NP
renderLinkEnd(facesContext, component);
}
protected void renderCommandLinkStart(FacesContext facesContext, UIComponent component,
String clientId,
Object value,
String style,
String styleClass)
throws IOException
{
ResponseWriter writer = facesContext.getResponseWriter();
String[] anchorAttrsToRender;
if (JavascriptUtils.isJavascriptAllowed(facesContext.getExternalContext()))
{
renderJavaScriptAnchorStart(facesContext, writer, component, clientId);
anchorAttrsToRender = HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES_WITHOUT_ONCLICK_WITHOUT_STYLE;
}
else
{
renderNonJavaScriptAnchorStart(facesContext, writer, component, clientId);
anchorAttrsToRender = HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES_WITHOUT_STYLE;
}
writer.writeAttribute(HTML.ID_ATTR, clientId, null);
HtmlRendererUtils.renderHTMLAttributes(writer, component,
anchorAttrsToRender);
HtmlRendererUtils.renderHTMLAttribute(writer, HTML.STYLE_ATTR, HTML.STYLE_ATTR,
style);
HtmlRendererUtils.renderHTMLAttribute(writer, HTML.STYLE_CLASS_ATTR, HTML.STYLE_CLASS_ATTR,
styleClass);
// render value as required by JSF 1.1 renderkitdocs
if(value != null)
{
writer.writeText(value.toString(), JSFAttr.VALUE_ATTR);
}
}
protected void renderJavaScriptAnchorStart(FacesContext facesContext,
ResponseWriter writer,
UIComponent component,
String clientId)
throws IOException
{
//Find form
UIComponent parent = component.getParent();
while (parent != null && !(parent instanceof UIForm))
{
parent = parent.getParent();
}
UIForm nestingForm = null;
String formName;
DummyFormResponseWriter dummyFormResponseWriter;
if (parent != null)
{
//link is nested inside a form
nestingForm = (UIForm)parent;
formName = nestingForm.getClientId(facesContext);
dummyFormResponseWriter = null;
}
else
{
//not nested in form, we must add a dummy form at the end of the document
formName = DummyFormUtils.DUMMY_FORM_NAME;
dummyFormResponseWriter = DummyFormUtils.getDummyFormResponseWriter(facesContext);
dummyFormResponseWriter.setWriteDummyForm(true);
}
StringBuffer onClick = new StringBuffer();
String commandOnclick;
if (component instanceof HtmlCommandLink)
{
commandOnclick = ((HtmlCommandLink)component).getOnclick();
}
else
{
commandOnclick = (String)component.getAttributes().get(HTML.ONCLICK_ATTR);
}
if (commandOnclick != null)
{
onClick.append(commandOnclick);
onClick.append(';');
}
//call the clear_<formName> method
onClick.append(HtmlRendererUtils.getClearHiddenCommandFormParamsFunctionName(formName)).append("();");
String jsForm = "document.forms['" + formName + "']";
if (MyfacesConfig.getCurrentInstance(facesContext.getExternalContext()).isAutoScroll())
{
JavascriptUtils.appendAutoScrollAssignment(onClick, formName);
}
//add id parameter for decode
String hiddenFieldName = HtmlRendererUtils.getHiddenCommandLinkFieldName(formName);
onClick.append(jsForm);
onClick.append(".elements['").append(hiddenFieldName).append("']");
onClick.append(".value='").append(clientId).append("';");
if (nestingForm != null)
{
HtmlFormRendererBase.addHiddenCommandParameter(nestingForm, hiddenFieldName);
}
else
{
dummyFormResponseWriter.addDummyFormParameter(hiddenFieldName);
}
//add child parameters
for (Iterator it = component.getChildren().iterator(); it.hasNext(); )
{
UIComponent child = (UIComponent)it.next();
if (child instanceof UIParameter)
{
String name = ((UIParameter)child).getName();
Object value = ((UIParameter)child).getValue();
renderLinkParameter(dummyFormResponseWriter, name, value, onClick, jsForm, nestingForm);
}
}
// target
// todo: can we eliminate the if and rely on the second call only ?
// see MYFACES-310, specifically the comment by Emond Papegaaij. Will try
// after testing framework is in place & tests are written.
String target = null;
if (component instanceof HtmlCommandLink)
{
target = ((HtmlCommandLink)component).getTarget();
}
else
{
target = (String)component.getAttributes().get(HTML.TARGET_ATTR);
}
if (target != null && target.trim().length() > 0) {
onClick.append(jsForm);
onClick.append(".target='");
onClick.append(target);
onClick.append("';");
}
// onSubmit
onClick.append("if(").append(jsForm).append(".onsubmit){if(").append(jsForm).append(".onsubmit()) "+jsForm+".submit();}else{");
//submit
onClick.append(jsForm);
onClick.append(".submit();}return false;"); //return false, so that browser does not handle the click
writer.startElement(HTML.ANCHOR_ELEM, component);
writer.writeURIAttribute(HTML.HREF_ATTR, "#", null);
writer.writeAttribute(HTML.ONCLICK_ATTR, onClick.toString(), null);
}
protected void renderNonJavaScriptAnchorStart(FacesContext facesContext,
ResponseWriter writer,
UIComponent component,
String clientId)
throws IOException
{
ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
String viewId = facesContext.getViewRoot().getViewId();
String path = viewHandler.getActionURL(facesContext, viewId);
StringBuffer hrefBuf = new StringBuffer(path);
//add clientId parameter for decode
if (path.indexOf('?') == -1)
{
hrefBuf.append('?');
}
else
{
hrefBuf.append('&');
}
String hiddenFieldName = HtmlRendererUtils.getHiddenCommandLinkFieldName(HtmlRendererUtils.getFormName(component, facesContext));
hrefBuf.append(hiddenFieldName);
hrefBuf.append('=');
hrefBuf.append(clientId);
if (component.getChildCount() > 0)
{
addChildParametersToHref(component, hrefBuf,
false, //not the first url parameter
writer.getCharacterEncoding());
}
StateManager stateManager = facesContext.getApplication().getStateManager();
if (stateManager.isSavingStateInClient(facesContext))
{
hrefBuf.append("&");
hrefBuf.append(URL_STATE_MARKER);
}
String href = facesContext.getExternalContext().encodeActionURL(hrefBuf.toString());
writer.startElement(HTML.ANCHOR_ELEM, component);
writer.writeURIAttribute(HTML.HREF_ATTR,
facesContext.getExternalContext().encodeActionURL(href),
null);
}
private void addChildParametersToHref(UIComponent linkComponent,
StringBuffer hrefBuf,
boolean firstParameter,
String charEncoding)
throws IOException
{
for (Iterator it = linkComponent.getChildren().iterator(); it.hasNext(); )
{
UIComponent child = (UIComponent)it.next();
if (child instanceof UIParameter)
{
String name = ((UIParameter)child).getName();
Object value = ((UIParameter)child).getValue();
addParameterToHref(name, value, hrefBuf, firstParameter, charEncoding);
firstParameter = false;
}
}
}
protected void renderOutputLinkStart(FacesContext facesContext, UIOutput output)
throws IOException
{
ResponseWriter writer = facesContext.getResponseWriter();
//calculate href
String href = RendererUtils.getStringValue(facesContext, output);
if (output.getChildCount() > 0)
{
StringBuffer hrefBuf = new StringBuffer(href);
addChildParametersToHref(output, hrefBuf,
(href.indexOf('?') == -1), //first url parameter?
writer.getCharacterEncoding());
href = hrefBuf.toString();
}
href = facesContext.getExternalContext().encodeResourceURL(href); //TODO: or encodeActionURL ?
//write anchor
writer.startElement(HTML.ANCHOR_ELEM, output);
writer.writeAttribute(HTML.ID_ATTR, output.getClientId(facesContext), null);
writer.writeURIAttribute(HTML.HREF_ATTR, href, null);
HtmlRendererUtils.renderHTMLAttributes(writer, output, HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES);
writer.flush();
}
private void renderLinkParameter(DummyFormResponseWriter dummyFormResponseWriter,
String name,
Object value,
StringBuffer onClick,
String jsForm,
UIForm nestingForm)
{
if (name == null)
{
throw new IllegalArgumentException("Unnamed parameter value not allowed within command link.");
}
onClick.append(jsForm);
onClick.append(".elements['").append(name).append("']");
//UIParameter is no ValueHolder, so no conversion possible
String strParamValue = value != null ? value.toString() : ""; //TODO: Use Converter?
onClick.append(".value='").append(strParamValue).append("';");
if (nestingForm != null)
{
//renderHiddenParam(writer, name);
HtmlFormRendererBase.addHiddenCommandParameter(nestingForm, name);
}
else
{
dummyFormResponseWriter.addDummyFormParameter(name);
}
}
private static void addParameterToHref(String name, Object value, StringBuffer hrefBuf, boolean firstParameter, String charEncoding)
throws UnsupportedEncodingException
{
if (name == null)
{
throw new IllegalArgumentException("Unnamed parameter value not allowed within command link.");
}
hrefBuf.append(firstParameter ? '?' : '&');
hrefBuf.append(URLEncoder.encode(name, charEncoding));
hrefBuf.append('=');
if (value != null)
{
//UIParameter is no ConvertibleValueHolder, so no conversion possible
hrefBuf.append(URLEncoder.encode(value.toString(), charEncoding));
}
}
protected void renderLinkEnd(FacesContext facesContext, UIComponent component)
throws IOException
{
ResponseWriter writer = facesContext.getResponseWriter();
// force separate end tag
writer.writeText("", null);
writer.endElement(HTML.ANCHOR_ELEM);
}
}