javax.faces.component.behavior.AjaxBehavior Maven / Gradle / Ivy
Show all versions of javax.faces-api Show documentation
/* * 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; /** *
If the value of delay is the literal string *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 SetHINTS = 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
* * @return unmodifiable set containing the hint {@link ClientBehaviorHint} *Set
containing * the {@link ClientBehaviorHint}SUBMITTING
.SUBMITTING
. * * @since 2.0 */ public SetgetHints() { 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 theexecute
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 CollectiongetExecute() { return getCollectionValue(EXECUTE, execute); } /** * Sets the component identifiers that * will be used to identify components that should be * processed during the
* * @param execute the ids of components to execute * * @since 2.0 */ public void setExecute(Collectionexecute
phase of the request * processing lifecycle.execute) { this.execute = copyToList(execute); clearInitialState(); } /** * Returns the delay value, or
* * @since 2.2 */ public String getDelay() { return (String) eval(DELAY, delay); } /** *null
* if no value was set.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.
'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 therender
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 CollectiongetRender() { return getCollectionValue(RENDER, render); } /** * Sets the component identifiers that * will be used to identify components that should be * processed during the
* * @param render the ids of components to render * * @since 2.0 */ public void setRender(Collectionrender
phase of the request * processing lifecycle.render) { this.render = copyToList(render); clearInitialState(); } /** * * Return the resetValues status of this behavior.
* * @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.
* * @since 2.2 */ public void setResetValues(boolean resetValues) { this.resetValues = resetValues; 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 * behavior.
* * @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
* * @since 2.0 */ public boolean isImmediateSet() { return ((immediate != null) || (getValueExpression(IMMEDIATE) != null)); } /** *AjaxBehavior
. *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. *
* * @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} * * @throws NullPointerException ifname
* isnull
*/ 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, ornull
* to remove any currently set {@link ValueExpression} * * @throws NullPointerException ifname
* isnull
*/ 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 iflistener
* isnull
* * @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 iflistener
* isnull
* * @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, Mapbindings) { // 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 (DELAY.equals(propertyName)) { delay = (String)value; } else if (ONERROR.equals(propertyName)) { onerror = (String)value; } else if (IMMEDIATE.equals(propertyName)) { immediate = (Boolean)value; } else if (RESET_VALUES.equals(propertyName)) { resetValues = (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 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(" "); }