blob: a8afb1339cdef2a7eaa083dc7aac4c0f870bec42 [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.renderkit.html;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.RandomAccess;
import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.component.UIParameter;
import javax.faces.component.behavior.ClientBehaviorContext;
import javax.faces.component.behavior.ClientBehaviorHolder;
import javax.faces.component.html.HtmlCommandScript;
import javax.faces.component.search.SearchExpressionContext;
import javax.faces.component.search.SearchExpressionHandler;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.event.ActionEvent;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFRenderer;
import org.apache.myfaces.component.search.MyFacesSearchExpressionHints;
import org.apache.myfaces.renderkit.html.base.ClientBehaviorRendererUtils;
import org.apache.myfaces.renderkit.html.util.HTML;
import org.apache.myfaces.renderkit.html.base.HtmlRenderer;
import org.apache.myfaces.renderkit.html.base.HtmlRendererUtils;
import org.apache.myfaces.renderkit.html.util.JavascriptContext;
import org.apache.myfaces.renderkit.html.util.JavascriptUtils;
import org.apache.myfaces.renderkit.html.util.ResourceUtils;
import org.apache.myfaces.util.ComponentUtils;
import org.apache.myfaces.util.lang.StringUtils;
import org.apache.myfaces.util.SharedStringBuilder;
@JSFRenderer(renderKitId = "HTML_BASIC", family = "javax.faces.Command", type = "javax.faces.Script")
public class HtmlCommandScriptRenderer extends HtmlRenderer
{
private static final String AJAX_KEY_ONERROR = "onerror";
private static final String AJAX_KEY_ONEVENT = "onevent";
private static final String AJAX_KEY_EXECUTE = "execute";
private static final String AJAX_KEY_RENDER = "render";
private static final String AJAX_KEY_RESETVALUES = "resetValues";
private static final String AJAX_VAL_THIS = "this";
private static final String JS_AJAX_REQUEST = "jsf.ajax.request";
private static final String AJAX_SB = "oam.renderkit.AJAX_SB";
private static final String AJAX_PARAM_SB = "oam.renderkit.AJAX_PARAM_SB";
@Override
public void encodeBegin(FacesContext context, UIComponent component) throws IOException
{
super.encodeBegin(context, component);
HtmlCommandScript commandScript = (HtmlCommandScript) component;
ResponseWriter writer = context.getResponseWriter();
ResourceUtils.renderDefaultJsfJsInlineIfNecessary(context, writer);
writer.startElement(HTML.SPAN_ELEM, component);
writer.writeAttribute(HTML.ID_ATTR, component.getClientId(context), null);
writer.startElement(HTML.SCRIPT_ELEM, component);
writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
JavascriptContext script = new JavascriptContext();
//Write content
String cmdName = commandScript.getName();
String name;
if (cmdName != null && cmdName.length() > 0)
{
name = JavascriptUtils.getValidJavascriptName(cmdName, true);
}
else
{
name = JavascriptUtils.getValidJavascriptName(component.getClientId(context), true);
}
script.prettyLine();
script.increaseIndent();
script.append("var "+name+" = function(o){var o=(typeof o==='object')&&o?o:{};");
script.prettyLine();
Collection<ClientBehaviorContext.Parameter> eventParameters = null;
//eventParameters.add(new ClientBehaviorContext.Parameter("params", "o"));
ClientBehaviorContext ccc = ClientBehaviorContext.createClientBehaviorContext(
context, component, "action",
commandScript.getClientId(context), eventParameters);
script.append(makeAjax(context, ccc, commandScript).toString());
script.decreaseIndent();
script.append("}");
if (commandScript.isAutorun())
{
script.append(";");
script.append("myfaces._impl.core._Runtime.addOnLoad(window,");
script.append(name);
script.append(");");
}
writer.writeText(script.toString(), null);
}
@Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException
{
super.encodeEnd(context, component); //
ResponseWriter writer = context.getResponseWriter();
writer.endElement(HTML.SCRIPT_ELEM);
writer.endElement(HTML.SPAN_ELEM);
}
@Override
public void decode(FacesContext facesContext, UIComponent component)
{
super.decode(facesContext, component);
HtmlCommandScript commandScript = (HtmlCommandScript) component;
if (HtmlRendererUtils.isDisabled(component) || !commandScript.isRendered())
{
return;
}
Map<String, String> paramMap = facesContext.getExternalContext().getRequestParameterMap();
String behaviorEventName = paramMap.get(ClientBehaviorContext.BEHAVIOR_EVENT_PARAM_NAME);
if (behaviorEventName != null)
{
String sourceId = paramMap.get(ClientBehaviorContext.BEHAVIOR_SOURCE_PARAM_NAME);
String componentClientId = component.getClientId(facesContext);
String clientId = sourceId;
if (sourceId.startsWith(componentClientId) && sourceId.length() > componentClientId.length())
{
String item = sourceId.substring(componentClientId.length()+1);
// If is item it should be an integer number, otherwise it can be related to a child
// component, because that could conflict with the clientId naming convention.
if (StringUtils.isInteger(item))
{
clientId = componentClientId;
}
}
if (component.getClientId(facesContext).equals(clientId))
{
boolean disabled = HtmlRendererUtils.isDisabled(component);
UIForm form = ComponentUtils.closest(UIForm.class, component);
boolean activateActionEvent = false;
if (form != null && !disabled)
{
String reqValue = (String) facesContext.getExternalContext().getRequestParameterMap().get(
HtmlRendererUtils.getHiddenCommandLinkFieldName(form, facesContext));
activateActionEvent = reqValue != null && reqValue.equals(clientId)
|| HtmlRendererUtils.isPartialOrBehaviorSubmit(facesContext, clientId);
}
if (activateActionEvent)
{
component.queueEvent(new ActionEvent(component));
}
}
}
if (component instanceof ClientBehaviorHolder && !HtmlRendererUtils.isDisabled(component))
{
ClientBehaviorRendererUtils.decodeClientBehaviors(facesContext, component);
}
}
/**
* builds the generic ajax call depending upon
* the ajax behavior parameters
*
* @param context the Client behavior context
* @param behavior the behavior
* @param commandScript the component
* @return a fully working javascript with calls into jsf.js
*/
private StringBuilder makeAjax(FacesContext facesContext, ClientBehaviorContext context,
HtmlCommandScript commandScript)
{
StringBuilder retVal = SharedStringBuilder.get(context.getFacesContext(), AJAX_SB, 60);
StringBuilder paramBuffer = SharedStringBuilder.get(context.getFacesContext(), AJAX_PARAM_SB, 20);
SearchExpressionContext searchExpressionContext = SearchExpressionContext.createSearchExpressionContext(
context.getFacesContext(), context.getComponent(),
MyFacesSearchExpressionHints.SET_RESOLVE_CLIENT_SIDE_RESOLVE_SINGLE_COMPONENT, null);
String executes = resolveExpressionsAsParameter(paramBuffer, AJAX_KEY_EXECUTE, commandScript.getExecute(),
searchExpressionContext);
String render = resolveExpressionsAsParameter(paramBuffer, AJAX_KEY_RENDER, commandScript.getRender(),
searchExpressionContext);
String onError = commandScript.getOnerror();
if (StringUtils.isNotBlank(onError))
{
paramBuffer.setLength(0);
paramBuffer.append(AJAX_KEY_ONERROR);
paramBuffer.append(':');
paramBuffer.append(onError);
onError = paramBuffer.toString();
}
else
{
onError = null;
}
String onEvent = commandScript.getOnevent();
if (StringUtils.isNotBlank(onEvent))
{
paramBuffer.setLength(0);
paramBuffer.append(AJAX_KEY_ONEVENT);
paramBuffer.append(':');
paramBuffer.append(onEvent);
onEvent = paramBuffer.toString();
}
else
{
onEvent = null;
}
String resetValues = null;
if (Boolean.TRUE.equals(commandScript.getResetValues()))
{
paramBuffer.setLength(0);
paramBuffer.append(AJAX_KEY_RESETVALUES);
paramBuffer.append(':');
paramBuffer.append("true");
resetValues = paramBuffer.toString();
}
String sourceId = null;
if (context.getSourceId() == null)
{
sourceId = AJAX_VAL_THIS;
}
else
{
paramBuffer.setLength(0);
paramBuffer.append('\'');
paramBuffer.append(context.getSourceId());
paramBuffer.append('\'');
sourceId = paramBuffer.toString();
if (!context.getSourceId().trim().equals(
context.getComponent().getClientId(context.getFacesContext())))
{
// Check if sourceId is not a clientId and there is no execute set
UIComponent ref = context.getComponent();
ref = (ref.getParent() == null) ? ref : ref.getParent();
UIComponent instance = null;
try
{
instance = ref.findComponent(context.getSourceId());
}
catch (IllegalArgumentException e)
{
// No Op
}
if (instance == null && executes == null)
{
// set the clientId of the component so the behavior can be decoded later,
// otherwise the behavior will fail
executes = resolveExpressionsAsParameter(paramBuffer, AJAX_KEY_EXECUTE,
context.getComponent().getClientId(context.getFacesContext()), searchExpressionContext);
}
}
}
String event = context.getEventName();
retVal.append(JS_AJAX_REQUEST);
retVal.append('(');
retVal.append(sourceId);
retVal.append(",window.event,myfaces._impl._util._Lang.mixMaps(");
Collection<ClientBehaviorContext.Parameter> params = context.getParameters();
int paramSize = (params != null) ? params.size() : 0;
List<String> parameterList = new ArrayList<>(paramSize + 2);
if (executes != null)
{
parameterList.add(executes);
}
if (render != null)
{
parameterList.add(render);
}
if (onError != null)
{
parameterList.add(onError);
}
if (onEvent != null)
{
parameterList.add(onEvent);
}
if (resetValues != null)
{
parameterList.add(resetValues);
}
if (paramSize > 0)
{
/**
* see ClientBehaviorContext.html of the spec
* the param list has to be added in the post back
*/
// params are in 99% RamdonAccess instace created in
// HtmlRendererUtils.getClientBehaviorContextParameters(Map<String, String>)
if (params instanceof RandomAccess)
{
List<ClientBehaviorContext.Parameter> list = (List<ClientBehaviorContext.Parameter>) params;
for (int i = 0, size = list.size(); i < size; i++)
{
ClientBehaviorContext.Parameter param = list.get(i);
append(paramBuffer, parameterList, param.getName(), param.getValue());
}
}
else
{
for (ClientBehaviorContext.Parameter param : params)
{
append(paramBuffer, parameterList, param.getName(), param.getValue());
}
}
}
List<UIParameter> uiParams = HtmlRendererUtils.getValidUIParameterChildren(
facesContext, getChildren(commandScript), false, false);
if (uiParams != null && uiParams.size() > 0)
{
for (int i = 0, size = uiParams.size(); i < size; i++)
{
UIParameter param = uiParams.get(i);
append(paramBuffer, parameterList, param.getName(), param.getValue());
}
}
paramBuffer.setLength(0);
paramBuffer.append('\'');
paramBuffer.append(ClientBehaviorContext.BEHAVIOR_EVENT_PARAM_NAME);
paramBuffer.append("\':\'");
paramBuffer.append(event);
paramBuffer.append('\'');
parameterList.add(paramBuffer.toString());
/**
* I assume here for now that the options are the same which also
* can be sent via the options attribute to javax.faces.ajax
* this still needs further clarifications but I assume so for now
*/
retVal.append(buildOptions(paramBuffer, parameterList));
//mixMaps
retVal.append(",o,false))");
return retVal;
}
private void append(StringBuilder paramBuffer, List<String> parameterList, String paramName, Object paramValue)
{
//Both name and value should be quoted
paramBuffer.setLength(0);
paramBuffer.append('\'');
paramBuffer.append(paramName);
paramBuffer.append("\':\'");
if (paramValue != null)
{
paramBuffer.append(paramValue.toString());
}
paramBuffer.append('\'');
parameterList.add(paramBuffer.toString());
}
private StringBuilder buildOptions(StringBuilder retVal, List<String> options)
{
retVal.setLength(0);
retVal.append('{');
boolean first = true;
for (int i = 0, size = options.size(); i < size; i++)
{
String option = options.get(i);
if (StringUtils.isNotBlank(option))
{
if (!first)
{
retVal.append(',');
}
else
{
first = false;
}
retVal.append(option);
}
}
retVal.append('}');
return retVal;
}
private String resolveExpressionsAsParameter(StringBuilder retVal, String target, String expressions,
SearchExpressionContext searchExpressionContext)
{
if (StringUtils.isNotBlank(expressions))
{
retVal.setLength(0);
retVal.append(target);
retVal.append(':');
retVal.append('\'');
SearchExpressionHandler handler =
searchExpressionContext.getFacesContext().getApplication().getSearchExpressionHandler();
List<String> clientIds =
handler.resolveClientIds(searchExpressionContext, expressions);
if (clientIds != null && !clientIds.isEmpty())
{
for (int i = 0; i < clientIds.size(); i++)
{
if (i > 0)
{
retVal.append(' ');
}
retVal.append(clientIds.get(i));
}
}
retVal.append('\'');
return retVal.toString();
}
return null;
}
}