javax.faces.component.behavior.AjaxBehavior Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
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 List execute;
private List render;
private Boolean disabled;
private Boolean immediate;
private Map bindings;
// ---------------------------------------------------------- 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
*/
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.
*
* @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.
*
* @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}.
*
* @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();
}
/**
* 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}.
*
* @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 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.
*
* @since 2.0
*/
public void setDisabled(boolean disabled) {
this.disabled = disabled;
clearInitialState();
}
/**
* Return the immediate status of this
* behaivor.
*
* @since 2.0
*/
public boolean isImmediate() {
Boolean result = (Boolean) eval(IMMEDIATE, immediate);
return ((result != null) ? result : false);
}
/**
* Sets the immediate status of this
* behavior.
*
* @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
.
*
*
* @since 2.0
*/
public boolean isImmediateSet() {
return ((immediate != null) || (getValueExpression(IMMEDIATE) != 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}
*
* @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[8];
values[0] = superState;
values[1] = onerror;
values[2] = onevent;
values[3] = disabled;
values[4] = immediate;
values[5] = saveList(execute);
values[6] = saveList(render);
values[7] = 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];
execute = restoreList(EXECUTE, values[5]);
render = restoreList(RENDER, values[6]);
bindings = restoreBindings(context, values[7]);
// 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 (ONEVENT.equals(propertyName)) {
onevent = (String)value;
} else if (ONERROR.equals(propertyName)) {
onerror = (String)value;
} else if (IMMEDIATE.equals(propertyName)) {
immediate = (Boolean)value;
} else if (DISABLED.equals(propertyName)) {
disabled = (Boolean)value;
} else if (EXECUTE.equals(propertyName)) {
execute = toList(propertyName, expression, value);
} else if (RENDER.equals(propertyName)) {
render = toList(propertyName, expression, value);
}
}
// 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.
List list;
if (ALL.equals(value)) {
list = ALL_LIST;
} else if (FORM.equals(value)){
list = FORM_LIST;
} else if (THIS.equals(value)) {
list = THIS_LIST;
} else if (NONE.equals(value)) {
list = NONE_LIST;
} else {
// RELEASE_PENDING i18n ;
throw new FacesException(value
+ " : Invalid id keyword specified for '"
+ propertyName
+ "' attribute");
}
return 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 DISABLED = "disabled";
private static final String EXECUTE = "execute";
private static final String RENDER = "render";
// 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(" ");
}