| /* |
| * 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.tobago.facelets; |
| |
| import org.apache.myfaces.tobago.component.Attributes; |
| import org.apache.myfaces.tobago.component.ClientBehaviors; |
| import org.apache.myfaces.tobago.internal.behavior.EventBehavior; |
| import org.apache.myfaces.tobago.internal.component.AbstractUIEvent; |
| |
| import javax.el.MethodExpression; |
| import javax.faces.component.PartialStateHolder; |
| import javax.faces.component.UIComponent; |
| import javax.faces.component.behavior.ClientBehaviorHolder; |
| import javax.faces.context.FacesContext; |
| import javax.faces.event.AbortProcessingException; |
| import javax.faces.event.AjaxBehaviorEvent; |
| import javax.faces.event.AjaxBehaviorListener; |
| import javax.faces.view.BehaviorHolderAttachedObjectHandler; |
| import javax.faces.view.facelets.ComponentConfig; |
| import javax.faces.view.facelets.ComponentHandler; |
| import javax.faces.view.facelets.FaceletContext; |
| import javax.faces.view.facelets.TagAttribute; |
| import javax.faces.view.facelets.TagAttributeException; |
| import javax.faces.view.facelets.TagException; |
| import java.io.IOException; |
| |
| /** |
| * 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. |
| * <p> |
| * Unless otherwise specified, all attributes accept static values or EL expressions. |
| * </p> |
| * <p> |
| * According to the documentation, the tag handler implementing this tag should meet |
| * the following conditions: |
| * </p> |
| * <ul> |
| * <li>Since this tag attach objects to UIComponent instances, and those instances |
| * implements Behavior interface, this component should implement |
| * BehaviorHolderAttachedObjectHandler interface.</li> |
| * <li>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.</li> |
| * <li>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.</li> |
| * </ul> |
| * |
| * @since 3.0.0 |
| */ |
| public class EventHandler extends TobagoComponentHandler implements BehaviorHolderAttachedObjectHandler { |
| |
| public static final Class<?>[] AJAX_BEHAVIOR_LISTENER_SIG = new Class<?>[]{AjaxBehaviorEvent.class}; |
| |
| private final TagAttribute event; |
| |
| // todo (see original AjaxHandler impl) private final boolean _wrapMode; |
| |
| public EventHandler(final ComponentConfig config) { |
| super(config); |
| event = getAttribute(Attributes.event.getName()); |
| } |
| |
| @Override |
| public void apply(final FaceletContext ctx, final UIComponent parent) |
| throws IOException { |
| |
| super.apply(ctx, parent); |
| |
| //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); |
| //todo } else if (UIComponent.isCompositeComponent(parent)) { |
| //todo 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. |
| //todo mctx.addAttachedObjectHandler(parent, this); |
| } else { |
| throw new TagException(this.tag, |
| "Parent is not composite component or of type ClientBehaviorHolder, type is: " |
| + parent); |
| } |
| } |
| |
| /** |
| * 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 { |
| return event.getValue(); |
| } |
| } |
| |
| /** |
| * This method should create an AjaxBehavior object and attach it to the |
| * parent component. |
| * <p> |
| * 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(final FacesContext context, final UIComponent parent) { |
| // Retrieve the current FaceletContext from FacesContext object |
| final FaceletContext faceletContext = (FaceletContext) context |
| .getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY); |
| |
| final ClientBehaviorHolder clientBehaviorHolder = (ClientBehaviorHolder) parent; |
| final UIComponent lastChild = parent.getChildren().stream().skip(parent.getChildCount() - 1) |
| .findFirst().orElse(null); |
| final AbstractUIEvent abstractUIEvent = lastChild instanceof AbstractUIEvent ? (AbstractUIEvent) lastChild : null; |
| |
| if (abstractUIEvent != null) { |
| String eventName = getEventName(); |
| if (eventName == null) { |
| eventName = clientBehaviorHolder.getDefaultEventName(); |
| if (eventName == null) { |
| throw new TagAttributeException(event, "eventName could not be defined for f:ajax tag with no wrap mode."); |
| } |
| } else if (!clientBehaviorHolder.getEventNames().contains(eventName)) { |
| throw new TagAttributeException(event, "event it is not a valid eventName defined for this component"); |
| } |
| |
| final EventBehavior eventBehavior = createBehavior(context); |
| eventBehavior.setFor(abstractUIEvent.getId()); |
| |
| clientBehaviorHolder.addClientBehavior(eventName, eventBehavior); |
| } |
| } |
| |
| protected EventBehavior createBehavior(final FacesContext context) { |
| return (EventBehavior) context.getApplication().createBehavior(EventBehavior.BEHAVIOR_ID); |
| } |
| |
| @Override |
| public void onComponentCreated( |
| final FaceletContext faceletContext, final UIComponent component, final UIComponent parent) { |
| super.onComponentCreated(faceletContext, component, parent); |
| |
| final AbstractUIEvent uiEvent = (AbstractUIEvent) component; |
| if (uiEvent.getEvent() == null) { |
| final ClientBehaviorHolder holder = (ClientBehaviorHolder) parent; |
| uiEvent.setEvent(ClientBehaviors.valueOf(holder.getDefaultEventName())); |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * Wraps a method expression in a AjaxBehaviorListener |
| */ |
| public static final class AjaxBehaviorListenerImpl implements |
| AjaxBehaviorListener, PartialStateHolder { |
| private MethodExpression expression; |
| private boolean transientBoolean; |
| private boolean initialStateMarked; |
| |
| public AjaxBehaviorListenerImpl() { |
| } |
| |
| public AjaxBehaviorListenerImpl(final MethodExpression expr) { |
| expression = expr; |
| } |
| |
| @Override |
| public void processAjaxBehavior(final AjaxBehaviorEvent event) |
| throws AbortProcessingException { |
| expression.invoke(FacesContext.getCurrentInstance().getELContext(), |
| new Object[]{event}); |
| } |
| |
| @Override |
| public boolean isTransient() { |
| return transientBoolean; |
| } |
| |
| @Override |
| public void restoreState(final FacesContext context, final Object state) { |
| if (state == null) { |
| return; |
| } |
| expression = (MethodExpression) state; |
| } |
| |
| @Override |
| public Object saveState(final FacesContext context) { |
| if (initialStateMarked()) { |
| return null; |
| } |
| return expression; |
| } |
| |
| @Override |
| public void setTransient(final boolean newTransientValue) { |
| transientBoolean = newTransientValue; |
| } |
| |
| @Override |
| public void clearInitialState() { |
| initialStateMarked = false; |
| } |
| |
| @Override |
| public boolean initialStateMarked() { |
| return initialStateMarked; |
| } |
| |
| @Override |
| public void markInitialState() { |
| initialStateMarked = true; |
| } |
| } |
| } |