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

org.apache.myfaces.view.facelets.tag.faces.core.AjaxHandler Maven / Gradle / Ivy

/*
 * 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.view.facelets.tag.faces.core;

import java.io.IOException;
import java.util.Collection;

import jakarta.el.MethodExpression;
import jakarta.el.ValueExpression;
import jakarta.faces.application.ResourceHandler;
import jakarta.faces.component.UIComponent;
import static jakarta.faces.component.UINamingContainer.getSeparatorChar;
import jakarta.faces.component.UniqueIdVendor;
import jakarta.faces.component.behavior.AjaxBehavior;
import jakarta.faces.component.behavior.ClientBehaviorHolder;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.AjaxBehaviorEvent;
import jakarta.faces.event.AjaxBehaviorListener;
import jakarta.faces.view.BehaviorHolderAttachedObjectHandler;
import jakarta.faces.view.facelets.ComponentHandler;
import jakarta.faces.view.facelets.FaceletContext;
import jakarta.faces.view.facelets.FaceletHandler;
import jakarta.faces.view.facelets.TagAttribute;
import jakarta.faces.view.facelets.TagAttributeException;
import jakarta.faces.view.facelets.TagConfig;
import jakarta.faces.view.facelets.TagException;
import jakarta.faces.view.facelets.TagHandler;
import java.util.ArrayList;
import java.util.function.Consumer;

import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFaceletAttribute;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFaceletTag;
import org.apache.myfaces.renderkit.html.util.ResourceUtils;
import org.apache.myfaces.view.facelets.AbstractFaceletContext;
import org.apache.myfaces.view.facelets.FaceletCompositionContext;
import org.apache.myfaces.view.facelets.tag.TagHandlerUtils;
import org.apache.myfaces.view.facelets.tag.composite.InsertChildrenHandler;
import org.apache.myfaces.view.facelets.tag.faces.ComponentSupport;
import org.apache.myfaces.view.facelets.tag.ui.DecorateHandler;
import org.apache.myfaces.view.facelets.tag.ui.IncludeHandler;
import org.apache.myfaces.view.facelets.tag.ui.InsertHandler;
import org.apache.myfaces.renderkit.html.util.ComponentAttrs;
import org.apache.myfaces.view.facelets.tag.composite.ClientBehaviorRedirectEventComponentWrapper;

/**
 * This tag creates an instance of AjaxBehavior, and associates it with the nearest 
 * parent UIComponent that implements ClientBehaviorHolder interface. This tag can
 * be used on single or composite components.
 * 

* Unless otherwise specified, all attributes accept static values or EL expressions. *

*

* According to the documentation, the tag handler implementing this tag should meet * the following conditions: *

*
    *
  • Since this tag attach objects to UIComponent instances, and those instances * implements Behavior interface, this component should implement * BehaviorHolderAttachedObjectHandler interface.
  • *
  • f:ajax does not support binding property. In theory we should do something similar * to f:convertDateTime tag does: extends from ConverterHandler and override setAttributes * method, but in this case BehaviorTagHandlerDelegate has binding property defined, so * if we extend from BehaviorHandler we add binding support to f:ajax.
  • *
  • This tag works as a attached object handler, but note on the api there is no component * to define a target for a behavior. See comment inside apply() method.
  • *
* @author Leonardo Uribe (latest modification by $Author$) * @version $Revision$ $Date$ */ @JSFFaceletTag(name = "f:ajax") public class AjaxHandler extends TagHandler implements BehaviorHolderAttachedObjectHandler { public final static Class[] AJAX_BEHAVIOR_LISTENER_SIG = new Class[] { AjaxBehaviorEvent.class }; /** * Constant used to check if in the current build view it has been rendered the standard jsf javascript * library. It is necessary to remove this key from facesContext attribute map after build, to keep * working this code for next views to be built. */ public final static String FACES_JS_DYNAMICALLY_ADDED = "org.apache.myfaces.FACES_JS_DYNAMICALLY_ADDED"; @JSFFaceletAttribute(name = "disabled", className = "jakarta.el.ValueExpression", deferredValueType = "java.lang.Boolean") private final TagAttribute disabled; @JSFFaceletAttribute(name = "event", className = "jakarta.el.ValueExpression", deferredValueType = "java.lang.String") private final TagAttribute event; @JSFFaceletAttribute(name = "execute", className = "jakarta.el.ValueExpression", deferredValueType = "java.lang.Object") private final TagAttribute execute; @JSFFaceletAttribute(name = "immediate", className = "jakarta.el.ValueExpression", deferredValueType = "java.lang.Boolean") private final TagAttribute immediate; @JSFFaceletAttribute(name = "listener", className = "jakarta.el.MethodExpression", deferredMethodSignature = "public void m(jakarta.faces.event.AjaxBehaviorEvent evt) " + "throws jakarta.faces.event.AbortProcessingException") private final TagAttribute listener; @JSFFaceletAttribute(name = "onevent", className = "jakarta.el.ValueExpression", deferredValueType = "java.lang.String") private final TagAttribute onevent; @JSFFaceletAttribute(name = "onerror", className = "jakarta.el.ValueExpression", deferredValueType = "java.lang.String") private final TagAttribute onerror; @JSFFaceletAttribute(name = "render", className = "jakarta.el.ValueExpression", deferredValueType = "java.lang.Object") private final TagAttribute render; @JSFFaceletAttribute(name = "delay", className = "jakarta.el.ValueExpression", deferredValueType = "java.lang.String") private final TagAttribute delay; @JSFFaceletAttribute(name = "resetValues", className = "jakarta.el.ValueExpression", deferredValueType = "java.lang.Boolean") private final TagAttribute resetValues; private final boolean wrappingMode; public AjaxHandler(TagConfig config) { super(config); disabled = getAttribute("disabled"); event = getAttribute("event"); execute = getAttribute("execute"); immediate = getAttribute("immediate"); listener = getAttribute("listener"); onerror = getAttribute("onerror"); onevent = getAttribute("onevent"); render = getAttribute("render"); delay = getAttribute("delay"); resetValues = getAttribute("resetValues"); // According to the spec, this tag works in two different ways: // 1. Apply an ajax behavior for a selected component in this way // // 2. Apply an ajax behavior for a group of components inside it // // // The first problem is how to discriminate if f:ajax tag is on a // "leaf" or if contain other components. // // One option is use the same strategy to cache instance for // handler: traverse the tree for instances of // ComponentHandler. If it is found, wrapMode is used otherwise // suppose f:ajax is the one wrapped by a component. Collection compHandlerList = TagHandlerUtils.findNextByType(nextHandler, ComponentHandler.class, InsertChildrenHandler.class, InsertHandler.class, DecorateHandler.class, IncludeHandler.class); wrappingMode = !compHandlerList.isEmpty(); } @Override public void apply(FaceletContext ctx, UIComponent parent) throws IOException { if (wrappingMode) { AbstractFaceletContext actx = (AbstractFaceletContext) ctx; // In this case it will be only applied to components inserted by // c:if or related tags, in other cases, ComponentTagHandlerDelegate should // not reapply ajax tag. actx.pushAjaxHandlerToStack(this); nextHandler.apply(ctx, parent); actx.popAjaxHandlerToStack(); registerFacesJsResource(ctx, parent); } else { //Apply only if we are creating a new component if (!ComponentHandler.isNew(parent)) { return; } if (parent instanceof ClientBehaviorHolder) { //Apply this handler directly over the parent applyAttachedObject(ctx.getFacesContext(), parent); } else if (UIComponent.isCompositeComponent(parent)) { FaceletCompositionContext mctx = FaceletCompositionContext.getCurrentInstance(ctx); // It is supposed that for composite components, this tag should // add itself as a target, but note that on whole api does not exists // some tag that expose client behaviors as targets for composite // components. In RI, there exists a tag called composite:clientBehavior, // but does not appear on spec or javadoc, maybe because this could be // understand as an implementation detail, after all there exists a key // called AttachedObjectTarget.ATTACHED_OBJECT_TARGETS_KEY that could be // used to create a tag outside jsf implementation to attach targets. mctx.addAttachedObjectHandler(parent, this); } else { throw new TagException(this.tag, "Parent is not composite component or of type ClientBehaviorHolder; Type is: " + parent); } registerFacesJsResource(ctx, parent); } } public static void registerFacesJsResource(FaceletContext ctx, UIComponent parent) { // Register the standard ajax library on the current page in this way: // // // // If no h:head component is in the page, we must anyway render the script inline, // so the only way to make sure we are doing this is add a outputScript component. // Note that call directly UIViewRoot.addComponentResource or use a listener // does not work in this case, because check this condition will requires // traverse the whole tree looking for h:head component. FacesContext facesContext = ctx.getFacesContext(); if (!facesContext.getAttributes().containsKey(FACES_JS_DYNAMICALLY_ADDED)) { UIComponent outputScript = facesContext.getApplication(). createComponent(facesContext, "jakarta.faces.Output", ResourceUtils.DEFAULT_SCRIPT_RENDERER_TYPE); outputScript.getAttributes().put(ComponentAttrs.NAME_ATTR, ResourceHandler.FACES_SCRIPT_RESOURCE_NAME); outputScript.getAttributes().put(ComponentAttrs.LIBRARY_ATTR, ResourceHandler.FACES_SCRIPT_LIBRARY_NAME); outputScript.getAttributes().put(ComponentAttrs.TARGET_ATTR, "head"); // Since this component will be relocated, we need a generated clientId from the // viewRoot, so when this one is relocated, its parent will be this UIViewRoot instance // and prevent a duplicate id exception. UniqueIdVendor uniqueIdVendor = ComponentSupport.getViewRoot(ctx, parent); // UIViewRoot implements UniqueIdVendor, so there is no need to cast to UIViewRoot // and call createUniqueId() String uid = uniqueIdVendor.createUniqueId(ctx.getFacesContext(),null); outputScript.setId(uid); parent.getChildren().add(outputScript); if (FaceletCompositionContext.getCurrentInstance(ctx).isMarkInitialState()) { //Call it only if we are using partial state saving outputScript.markInitialState(); } facesContext.getAttributes().put(FACES_JS_DYNAMICALLY_ADDED, Boolean.TRUE); } } /** * ViewDeclarationLanguage.retargetAttachedObjects uses it to check * if the the target to be processed is applicable for this handler */ @Override public String getEventName() { if (event == null) { return null; } else { if (event.isLiteral()) { return event.getValue(); } else { FaceletContext faceletContext = (FaceletContext) FacesContext.getCurrentInstance(). getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY); return event.getValueExpression(faceletContext, String.class).getValue(faceletContext); } } } /** * This method should create an AjaxBehavior object and attach it to the * parent component. * * Also, it should check if the parent can apply the selected AjaxBehavior * to the selected component through ClientBehaviorHolder.getEventNames() or * ClientBehaviorHolder.getDefaultEventName() */ @Override public void applyAttachedObject(FacesContext context, UIComponent parent) { FaceletContext faceletContext = (FaceletContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY); ClientBehaviorHolder cvh = (ClientBehaviorHolder) parent; String eventName = null; if (event != null) { if (event.isLiteral()) { eventName = event.getValue(); } else { eventName = event.getValueExpression(faceletContext, String.class).getValue(faceletContext); } } if (eventName == null) { eventName = cvh.getDefaultEventName(); if (eventName == null) { if (wrappingMode) { // No eventName defined, we can't apply this tag to this component, because // there is no event defined to attach it, but since we are in wrap mode // we have here the case that the component could not be the target // for this attached object. return; } else { throw new TagAttributeException(event, "eventName could not be defined for f:ajax tag with no wrap mode."); } } } else if (!cvh.getEventNames().contains(eventName)) { if (wrappingMode) { // The current component does not implement the event selected, // this ajax behavior cannot be applied, but we can't throw any exception // since we are in wrap mode and we have here the case that the // component could not be the target for this attached object. return; } else { throw new TagAttributeException(event, "event it is not a valid eventName defined for this component"); } } AjaxBehavior ajaxBehavior = (AjaxBehavior) context.getApplication().createBehavior(AjaxBehavior.BEHAVIOR_ID); setAttribute(faceletContext, ajaxBehavior, disabled, Boolean.class, (v) -> ajaxBehavior.setDisabled(v)); setAttribute(faceletContext, ajaxBehavior, execute, Object.class); setAttribute(faceletContext, ajaxBehavior, immediate, Boolean.class, (v) -> ajaxBehavior.setImmediate(v)); setAttribute(faceletContext, ajaxBehavior, onerror, String.class, (v) -> ajaxBehavior.setOnerror(v)); setAttribute(faceletContext, ajaxBehavior, onevent, String.class, (v) -> ajaxBehavior.setOnevent(v)); setAttribute(faceletContext, ajaxBehavior, render, Object.class); setAttribute(faceletContext, ajaxBehavior, delay, String.class, (v) -> ajaxBehavior.setDelay(v)); setAttribute(faceletContext, ajaxBehavior, resetValues, Boolean.class, (v) -> ajaxBehavior.setResetValues(v)); if (listener != null) { MethodExpression expr = listener.getMethodExpression( faceletContext, Void.TYPE, AJAX_BEHAVIOR_LISTENER_SIG); AjaxBehaviorListener abl = new AjaxBehaviorListenerImpl(expr); ajaxBehavior.addAjaxBehaviorListener(abl); } // remap @this to the composite targets if (parent instanceof ClientBehaviorRedirectEventComponentWrapper) { ValueExpression targets = ((ClientBehaviorRedirectEventComponentWrapper) parent).getTargets(); String targetsString = targets == null ? null : (String) targets.getValue(context.getELContext()); String[] targetsArray = targetsString == null ? null : targetsString.trim().split(" +"); if (targetsArray != null && targetsArray.length > 0) { String separatorChar = String.valueOf(getSeparatorChar(context)); // execute is required Collection execute = ajaxBehavior.getExecute(); if (execute.isEmpty() || execute.contains("@this")) { Collection newExecute = new ArrayList<>(execute); newExecute.remove("@this"); for (String target : targetsArray) { newExecute.add("@this" + separatorChar + target); } ajaxBehavior.setExecute(newExecute); } // render is optional Collection render = ajaxBehavior.getRender(); if (render.contains("@this")) { Collection newRender = new ArrayList<>(render); newRender.remove("@this"); for (String target : targetsArray) { newRender.add("@this" + separatorChar + target); } ajaxBehavior.setRender(newRender); } } } cvh.addClientBehavior(eventName, ajaxBehavior); } protected void setAttribute(FaceletContext faceletContext, AjaxBehavior behavior, TagAttribute attr, Class type) { setAttribute(faceletContext, behavior, attr, type, null); } protected void setAttribute(FaceletContext faceletContext, AjaxBehavior behavior, TagAttribute attr, Class type, Consumer setter) { if (attr != null) { if (!attr.isLiteral() || setter == null) { behavior.setValueExpression(attr.getLocalName(), attr.getValueExpression(faceletContext, type)); } else { if (type == Boolean.class) { ((Consumer) setter).accept(attr.getBoolean(faceletContext)); } else { setter.accept((T) attr.getValue(faceletContext)); } } } } /** * The documentation says this attribute should not be used since it is not * taken into account. Instead, getEventName is used on * ViewDeclarationLanguage.retargetAttachedObjects. */ @Override public String getFor() { return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy