javax.faces.component.behavior.AjaxBehavior Maven / Gradle / Ivy
Show all versions of jakarta.faces-api Show documentation
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package javax.faces.component.behavior;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.el.ELContext;
import javax.el.ELException;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.event.AjaxBehaviorListener;
/**
* An instance of this class
* is added as a {@link ClientBehavior} to a component using the {@link
* javax.faces.component.behavior.ClientBehaviorHolder#addClientBehavior}
* contract that components implement. The presence of this {@link
* ClientBehavior} will cause the rendering of JavaScript that produces
* an Ajax
request using the specification public
* JavaScript API when the component is rendered.
* If the component is an instance
* of {@link javax.faces.component.EditableValueHolder}, Where at all
* possible, the component must have the UI register the ajax event when
* the initial value is changed, not when focus is lost on the
* component.
*
*
* @since 2.0
*/
public class AjaxBehavior extends ClientBehaviorBase {
/**
* The standard id for this behavior.
*/
public static final String BEHAVIOR_ID = "javax.faces.behavior.Ajax";
private static final Set HINTS =
Collections.unmodifiableSet(EnumSet.of(ClientBehaviorHint.SUBMITTING));
private String onerror;
private String onevent;
private String delay;
private List execute;
private List render;
private Boolean disabled;
private Boolean immediate;
private Boolean resetValues;
private Map bindings;
public AjaxBehavior() {
}
// ---------------------------------------------------------- Public Methods
@Override
public String getRendererType() {
// We use the same sring for both the behavior id and the renderer
// type.
return BEHAVIOR_ID;
}
/**
*
* This method returns an unmodifiable Set
containing
* the {@link ClientBehaviorHint} SUBMITTING
.
*
* @return unmodifiable set containing the hint {@link ClientBehaviorHint}
* SUBMITTING
.
*
* @since 2.0
*/
@Override
public Set getHints() {
return HINTS;
}
/**
* Return the String
of
* JavaScript function name that will be used to identify
* the client callback function that should be run in the event of
* an error.
*
* @return the JavaScript function name of ONERROR
.
*
* @since 2.0
*/
public String getOnerror() {
return (String) eval(ONERROR, onerror);
}
/**
*
Sets the JavaScript function name
* that will be used to identify the client callback function that
* should be run in the event of an error.
*
* @param onerror the error handling function name
*
* @since 2.0
*/
public void setOnerror(String onerror) {
this.onerror = onerror;
clearInitialState();
}
/**
*
Return the String
of
* JavaScript function name that will be used to identify the
* client callback function that should be run on the occurance
* of a client-side event.
*
* @return the JavaScript function name of ONEVENT
.
*
* @since 2.0
*/
public String getOnevent() {
return (String) eval(ONEVENT, onevent);
}
/**
*
Sets the JavaScript function name
* that will be used to identify the client callback function that
* should be run in response to event activity.
*
* @param onevent the event handling function name
*
* @since 2.0
*/
public void setOnevent(String onevent) {
this.onevent = onevent;
clearInitialState();
}
/**
*
Return a non-empty
* Collection<String>
of component
* identifiers that will be used to identify components that should be
* processed during the execute
phase of the request
* processing lifecycle.
* Note that the returned collection may be unmodifiable. Modifications
* should be performed by calling {@link #setExecute}.
*
* @return the JavaScript function name of EXECUTE
.
*
* @since 2.0
*/
public Collection getExecute() {
return getCollectionValue(EXECUTE, execute);
}
/**
* Sets the component identifiers that
* will be used to identify components that should be
* processed during the execute
phase of the request
* processing lifecycle.
*
* @param execute the ids of components to execute
*
* @since 2.0
*/
public void setExecute(Collection execute) {
this.execute = copyToList(execute);
clearInitialState();
}
/**
* Returns the delay value, or null
* if no value was set.
*
* @return the delay value.
*
* @since 2.2
*/
public String getDelay() {
return (String) eval(DELAY, delay);
}
/**
* If less than
* delay milliseconds elapses between calls to
* request() only the most recent one is sent and all other
* requests are discarded. The default value of this option is
* 300. If the value of delay is the literal string
* 'none'
without the quotes, no delay is used.
*
* @param delay the ajax delay value
*
* @since 2.2
*/
public void setDelay(String delay) {
this.delay = delay;
clearInitialState();
}
/**
* Return a non-empty
* Collection<String>
of component
* identifiers that will be used to identify components that should be
* processed during the render
phase of the request
* processing lifecycle.
* Note that the returned collection may be unmodifiable. Modifications
* should be performed by calling {@link #setRender}.
*
* @return the ids of components to render.
*
* @since 2.0
*/
public Collection getRender() {
return getCollectionValue(RENDER, render);
}
/**
* Sets the component identifiers that
* will be used to identify components that should be
* processed during the render
phase of the request
* processing lifecycle.
*
* @param render the ids of components to render
*
* @since 2.0
*/
public void setRender(Collection render) {
this.render = copyToList(render);
clearInitialState();
}
/**
*
* Return the resetValues status of this behavior.
*
* @return the resetValues status.
*
* @since 2.2
*/
public boolean isResetValues() {
Boolean result = (Boolean) eval(RESET_VALUES, resetValues);
return ((result != null) ? result : false);
}
/**
*
* Set the resetValues status of this behavior.
*
* @param resetValues the resetValues status.
*
* @since 2.2
*/
public void setResetValues(boolean resetValues) {
this.resetValues = resetValues;
clearInitialState();
}
/**
* Return the disabled status of this behavior.
*
* @return the disabled status of this behavior.
*
* @since 2.0
*/
public boolean isDisabled() {
Boolean result = (Boolean) eval(DISABLED, disabled);
return ((result != null) ? result : false);
}
/**
* Sets the disabled status of this
* behavior.
*
* @param disabled the flag to be set.
*
* @since 2.0
*/
public void setDisabled(boolean disabled) {
this.disabled = disabled;
clearInitialState();
}
/**
* Return the immediate status of this
* behavior.
*
* @return the immediate status.
*
* @since 2.0
*/
public boolean isImmediate() {
Boolean result = (Boolean) eval(IMMEDIATE, immediate);
return ((result != null) ? result : false);
}
/**
* Sets the immediate status of this
* behavior.
*
* @param immediate the flag to be set.
*
* @since 2.0
*/
public void setImmediate(boolean immediate) {
this.immediate = immediate;
clearInitialState();
}
/**
* Tests whether the immediate attribute
* is specified. Returns true if the immediate attribute is specified,
* either as a locally set property or as a value expression. This
* information allows an associated client behavior renderer to fall back
* on the parent component's immediate status when immediate is not
* explicitly specified on the AjaxBehavior
.
*
*
* @return the flag whether the immediate attribute is specified.
*
* @since 2.0
*/
public boolean isImmediateSet() {
return ((immediate != null) || (getValueExpression(IMMEDIATE) != null));
}
/**
* Tests whether the resetValues attribute
* is specified. Returns true if the resetValues attribute is specified,
* either as a locally set property or as a value expression.
*
*
* @return the flag whether the resetValues attribute is specified.
*
* @since 2.2
*/
public boolean isResetValuesSet() {
return ((resetValues != null) || (getValueExpression(RESET_VALUES) != null));
}
/**
* Returns the {@link ValueExpression}
* used to calculate the value for the specified property name, if any.
*
*
* @param name Name of the property for which to retrieve a
* {@link ValueExpression}
*
* @return the {@link ValueExpression}.
*
* @throws NullPointerException if name
* is null
*/
public ValueExpression getValueExpression(String name) {
if (name == null) {
throw new NullPointerException();
}
return ((bindings == null) ? null : bindings.get(name));
}
/**
* Sets the {@link ValueExpression}
* used to calculate the value for the specified property name.
*
* @param name Name of the property for which to set a
* {@link ValueExpression}
* @param binding The {@link ValueExpression} to set, or null
* to remove any currently set {@link ValueExpression}
*
* @throws NullPointerException if name
* is null
*/
public void setValueExpression(String name, ValueExpression binding) {
if (name == null) {
throw new NullPointerException();
}
if (binding != null) {
if (binding.isLiteralText()) {
setLiteralValue(name, binding);
} else {
if (bindings == null) {
// We use a very small initial capacity on this HashMap.
// The goal is not to reduce collisions, but to keep the
// memory footprint small. It is very unlikely that an
// an AjaxBehavior would have more than 1 or 2 bound
// properties - and even if more are present, it's okay
// if we have some collisions - will still be fast.
bindings = new HashMap<>(6,1.0f);
}
bindings.put(name, binding);
}
} else {
if (bindings != null) {
bindings.remove(name);
if (bindings.isEmpty()) {
bindings = null;
}
}
}
clearInitialState();
}
/**
* Add the specified {@link AjaxBehaviorListener}
* to the set of listeners registered to receive event notifications
* from this {@link AjaxBehavior}.
*
* @param listener The {@link AjaxBehaviorListener} to be registered
*
* @throws NullPointerException if listener
* is null
*
* @since 2.0
*/
public void addAjaxBehaviorListener(AjaxBehaviorListener listener) {
addBehaviorListener(listener);
}
/**
* Remove the specified {@link AjaxBehaviorListener}
* from the set of listeners registered to receive event notifications
* from this {@link AjaxBehavior}.
*
* @param listener The {@link AjaxBehaviorListener} to be removed
*
* @throws NullPointerException if listener
* is null
*
* @since 2.0
*/
public void removeAjaxBehaviorListener(AjaxBehaviorListener listener) {
removeBehaviorListener(listener);
}
@Override
public Object saveState(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
Object[] values;
Object superState = super.saveState(context);
if (initialStateMarked()) {
if (superState == null) {
values = null;
} else {
values = new Object[] { superState };
}
} else {
values = new Object[10];
values[0] = superState;
values[1] = onerror;
values[2] = onevent;
values[3] = disabled;
values[4] = immediate;
values[5] = resetValues;
values[6] = delay;
values[7] = saveList(execute);
values[8] = saveList(render);
values[9] = saveBindings(context, bindings);
}
return values;
}
@Override
public void restoreState(FacesContext context, Object state) {
if (context == null) {
throw new NullPointerException();
}
if (state != null) {
Object[] values = (Object[]) state;
super.restoreState(context, values[0]);
if (values.length != 1) {
onerror = (String)values[1];
onevent = (String)values[2];
disabled = (Boolean)values[3];
immediate = (Boolean)values[4];
resetValues = (Boolean)values[5];
delay = (String)values[6];
execute = restoreList(EXECUTE, values[7]);
render = restoreList(RENDER, values[8]);
bindings = restoreBindings(context, values[9]);
// If we saved state last time, save state again next time.
clearInitialState();
}
}
}
// --------------------------------------------------------- Private Methods
// Utility for saving bindings state
private static Object saveBindings(FacesContext context,
Map bindings) {
// Note: This code is copied from UIComponentBase. In a future
// version of the JSF spec, it would be useful to define a
// attribute/property/bindings/state helper object that can be
// shared across components/behaviors/validaters/converters.
if (bindings == null) {
return (null);
}
Object values[] = new Object[2];
values[0] = bindings.keySet().toArray(new String[bindings.size()]);
Object[] bindingValues = bindings.values().toArray();
for (int i = 0; i < bindingValues.length; i++) {
bindingValues[i] = UIComponentBase.saveAttachedState(context, bindingValues[i]);
}
values[1] = bindingValues;
return (values);
}
// Utility for restoring bindings from state
private static Map restoreBindings(FacesContext context,
Object state) {
// Note: This code is copied from UIComponentBase. See note above
// in saveBindings().
if (state == null) {
return (null);
}
Object values[] = (Object[]) state;
String names[] = (String[]) values[0];
Object states[] = (Object[]) values[1];
Map bindings = new HashMap<>(names.length);
for (int i = 0; i < names.length; i++) {
bindings.put(names[i],
(ValueExpression) UIComponentBase.restoreAttachedState(context, states[i]));
}
return (bindings);
}
// Save the List, either as a String (single element) or as
// a String[] (multiple elements.
private static Object saveList(List list) {
if ((list == null) || list.isEmpty()) {
return null;
}
int size = list.size();
if (size == 1) {
return list.get(0);
}
return list.toArray(new String[size]);
}
// Restore the list from a String (single element) or a String[]
// (multiple elements)
private static List restoreList(String propertyName,
Object state) {
if (state == null) {
return null;
}
List list = null;
if (state instanceof String) {
list = toSingletonList(propertyName, (String)state);
} else if (state instanceof String[]) {
list = Collections.unmodifiableList(Arrays.asList((String[])state));
}
return list;
}
private Object eval(String propertyName, Object value) {
if (value != null) {
return value;
}
ValueExpression expression = getValueExpression(propertyName);
if (expression != null) {
FacesContext ctx = FacesContext.getCurrentInstance();
return expression.getValue(ctx.getELContext());
}
return null;
}
@SuppressWarnings("unchecked")
private Collection getCollectionValue(String propertyName,
Collection collection) {
if (collection!= null) {
return collection;
}
Collection result = null;
ValueExpression expression = getValueExpression(propertyName);
if (expression != null) {
FacesContext ctx = FacesContext.getCurrentInstance();
Object value = expression.getValue(ctx.getELContext());
if (value != null) {
if (value instanceof Collection) {
// Unchecked cast to Collection
return (Collection)value;
}
result = toList(propertyName, expression, value);
}
}
return result == null ? Collections.emptyList() : result;
}
// Sets a property, converting it from a literal
private void setLiteralValue(String propertyName,
ValueExpression expression) {
assert(expression.isLiteralText());
Object value;
ELContext context = FacesContext.getCurrentInstance().getELContext();
try {
value = expression.getValue(context);
} catch (ELException ele) {
throw new FacesException(ele);
}
if (null != propertyName) {
switch (propertyName) {
case ONEVENT:
onevent = (String)value;
break;
case DELAY:
delay = (String)value;
break;
case ONERROR:
onerror = (String)value;
break;
case IMMEDIATE:
immediate = (Boolean)value;
break;
case RESET_VALUES:
resetValues = (Boolean)value;
break;
case DISABLED:
disabled = (Boolean)value;
break;
case EXECUTE:
execute = toList(propertyName, expression, value);
break;
case RENDER:
render = toList(propertyName, expression, value);
break;
}
}
}
// Converts the specified object to a List
private static List toList(String propertyName,
ValueExpression expression,
Object value) {
if (value instanceof String) {
String strValue = (String)value;
// If the value contains no spaces, we can optimize.
// This is worthwhile, since the execute/render lists
// will often only contain a single value.
if (strValue.indexOf(' ') == -1) {
return toSingletonList(propertyName, strValue);
}
// We're stuck splitting up the string.
String[] values = SPLIT_PATTERN.split(strValue);
if ((values == null) || (values.length == 0)) {
return null;
}
// Note that we could create a Set out of the values if
// we care about removing duplicates. However, the
// presence of duplicates does not real harm. They will
// be consolidated during the partial view traversal. So,
// just create an list - garbage in, garbage out.
return Collections.unmodifiableList(Arrays.asList(values));
}
// RELEASE_PENDING i18n ;
throw new FacesException(expression.toString()
+ " : '"
+ propertyName
+ "' attribute value must be either a String or a Collection");
}
// Converts a String with no spaces to a singleton list
private static List toSingletonList(String propertyName,
String value) {
if ((null == value) || (value.length() == 0)) {
return null;
}
if (value.charAt(0) == '@') {
// These are very common, so we use shared copies
// of these collections instead of re-creating.
if (ALL.equals(value)) {
return ALL_LIST;
} else if (FORM.equals(value)){
return FORM_LIST;
} else if (THIS.equals(value)) {
return THIS_LIST;
} else if (NONE.equals(value)) {
return NONE_LIST;
}
}
return Collections.singletonList(value);
}
// Makes a defensive copy of the collection, converting to a List
// (to make state saving a bit easier).
private List copyToList(Collection collection) {
if ((collection == null) || collection.isEmpty()) {
return null;
}
return Collections.unmodifiableList(new ArrayList(collection));
}
// Property name constants
private static final String ONEVENT = "onevent";
private static final String ONERROR = "onerror";
private static final String IMMEDIATE = "immediate";
private static final String RESET_VALUES = "resetValues";
private static final String DISABLED = "disabled";
private static final String EXECUTE = "execute";
private static final String RENDER = "render";
private static final String DELAY = "delay";
// Id keyword constants
private static String ALL = "@all";
private static String FORM = "@form";
private static String THIS = "@this";
private static String NONE = "@none";
// Shared execute/render collections
private static List ALL_LIST = Collections.singletonList("@all");
private static List FORM_LIST = Collections.singletonList("@form");
private static List THIS_LIST = Collections.singletonList("@this");
private static List NONE_LIST = Collections.singletonList("@none");
// Pattern used for execute/render string splitting
private static Pattern SPLIT_PATTERN = Pattern.compile(" ");
}