All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javax.faces.component.behavior.AjaxBehavior Maven / Gradle / Ivy

Go to download

Jakarta Faces defines an MVC framework for building user interfaces for web applications, including UI components, state management, event handing, input validation, page navigation, and support for internationalization and accessibility.

There is a newer version: 4.1.0
Show newest version
/*
 * 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(" "); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy