blob: 4655b3484861b9f2ab2b7e1c1a58e77419229f34 [file] [log] [blame]
/*
* 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;
}
}
}