/*
* 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.wicket.behavior;
import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.IComponentAwareEventSink;
import org.apache.wicket.IRequestListener;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.IComponentAwareHeaderContributor;
import org.apache.wicket.markup.parser.XmlTag.TagType;
import org.apache.wicket.util.io.IClusterable;
import org.apache.wicket.util.lang.Args;
import org.danekja.java.util.function.serializable.SerializableBiConsumer;
import org.danekja.java.util.function.serializable.SerializableFunction;
/**
* Behaviors are kind of plug-ins for Components. They allow functionality to be added to a
* component and get essential events forwarded by the component. They can be bound to a concrete
* component (using the bind method which is called when the behavior is attached), but they don't
* need to. They can modify the components markup by changing the rendered ComponentTag. Behaviors
* can have their own models as well, and they are notified when these are to be detached by the
* component.
* <p>
* You also cannot modify a components model with a behavior.
* </p>
*
* @see IRequestListener
* @see org.apache.wicket.markup.html.IHeaderContributor
* @see org.apache.wicket.behavior.AbstractAjaxBehavior
* @see org.apache.wicket.AttributeModifier
*
* @author Ralf Ebert
* @author Eelco Hillenius
* @author Igor Vaynberg (ivaynberg)
*/
public abstract class Behavior
implements
IClusterable,
IComponentAwareEventSink,
IComponentAwareHeaderContributor
{
private static final long serialVersionUID = 1L;
/**
* Constructor
*/
public Behavior()
{
if (Application.exists())
{
Application.get().getBehaviorInstantiationListeners().onInstantiation(this);
}
}
/**
* Called when a component is about to render.
*
* @param component
* the component that has this behavior coupled
*/
public void beforeRender(Component component)
{
}
/**
* Called when a component that has this behavior coupled was rendered.
*
* @param component
* the component that has this behavior coupled
*/
public void afterRender(Component component)
{
}
/**
* Bind this handler to the given component. This method is called by the host component
* immediately after this behavior is added to it. This method is useful if you need to do
* initialization based on the component it is attached and you can't wait to do it at render
* time. Keep in mind that if you decide to keep a reference to the host component, it is not
* thread safe anymore, and should thus only be used in situations where you do not reuse the
* behavior for multiple components.
*
* @param component
* the component to bind to
*/
public void bind(Component component)
{
}
/**
* Notifies the behavior it is removed from the specified component
*
* @param component
* the component this behavior is unbound from
*/
public void unbind(Component component)
{
}
/**
* Allows the behavior to detach any state it has attached during request processing.
*
* @param component
* the component that initiates the detachment of this behavior
*/
public void detach(Component component)
{
}
/**
* In case an unexpected exception happened anywhere between
* {@linkplain #onComponentTag(org.apache.wicket.Component, org.apache.wicket.markup.ComponentTag)} and
* {@linkplain #afterRender(org.apache.wicket.Component)},
* onException() will be called for any behavior. Typically, if you clean up resources in
* {@link #afterRender(Component)}, you should do the same in the implementation of this method.
*
* @param component
* the component that has a reference to this behavior and during which processing
* the exception occurred
* @param exception
* the unexpected exception
*/
public void onException(Component component, RuntimeException exception)
{
}
/**
* This method returns false if the behavior generates a callback url (for example ajax
* behaviors)
*
* @param component
* the component that has this behavior coupled.
*
* @return boolean true or false.
*/
public boolean getStatelessHint(Component component)
{
if (this instanceof IRequestListener)
{
// this behavior implements a callback interface, so it cannot be stateless
return false;
}
return true;
}
/**
* Called when a components is rendering and wants to render this behavior. If false is returned
* this behavior will be ignored.
*
* @param component
* the component that has this behavior coupled
*
* @return true if this behavior must be executed/rendered
*/
public boolean isEnabled(Component component)
{
return true;
}
/**
* Called any time a component that has this behavior registered is rendering the component tag.
*
* @param component
* the component that renders this tag currently
* @param tag
* the tag that is rendered
*/
public void onComponentTag(Component component, ComponentTag tag)
{
}
/**
* Specifies whether or not this behavior is temporary. Temporary behaviors are removed at the
* end of request and never reattached. Such behaviors are useful for modifying component
* rendering only when it renders next. Usecases include javascript effects, initial clientside
* dom setup, etc.
*
* @param component
*
* @return true if this behavior is temporary
*/
public boolean isTemporary(Component component)
{
return false;
}
/**
* Checks whether or not an {@link IRequestListener} can be invoked on this behavior. For further
* information please read the javadoc on {@link Component#canCallListener()},
* this method has the same semantics.
*
* WARNING: Read the javadoc of {@link Component#canCallListener()} for important
* security-related information.
*
* @param component
* component this behavior is attached to
* @return {@literal true} iff the listener method can be invoked
*/
public boolean canCallListener(Component component)
{
return isEnabled(component) && component.canCallListener();
}
/**
* Render to the web response whatever the component wants to contribute to the head section.
*
* @param component
*
* @param response
* Response object
*/
@Override
public void renderHead(Component component, IHeaderResponse response)
{
}
/**
* Called immediately after the onConfigure method in a component. Since this is before the
* rendering cycle has begun, the behavior can modify the configuration of the component (i.e.
* setVisible(false))
*
* @param component
* the component being configured
*/
public void onConfigure(Component component)
{
}
/**
* Called to notify the behavior about any events sent to the component
*
* @see org.apache.wicket.IComponentAwareEventSink#onEvent(org.apache.wicket.Component,
* org.apache.wicket.event.IEvent)
*/
@Override
public void onEvent(Component component, IEvent<?> event)
{
}
/**
* Called to notify that the component is being removed from its parent
* @param component
* the removed component
*/
public void onRemove(Component component)
{
}
/**
* Creates a {@link Behavior} that uses the given {@code SerializableConsumer consumer} to do
* something with the component's tag.
*
* <p>
* Usage:<br/>
* <code>component.add(onTag(tag -> tag.put(key, value)));</code>
* </p>
*
* @param onTagConsumer
* the {@code SerializableConsumer} that accepts the {@link ComponentTag}
* @return The created behavior
*/
public static Behavior onTag(SerializableBiConsumer<Component, ComponentTag> onTagConsumer)
{
Args.notNull(onTagConsumer, "onTagConsumer");
return new Behavior()
{
@Override
public void onComponentTag(Component component, ComponentTag tag)
{
onTagConsumer.accept(component, tag);
}
};
}
/**
* Creates a {@link Behavior} that uses the given {@code SerializableFunction function} to do
* something with a component's attribute.
*
* <p>
* Usage:<br/>
* <code>component.add(onAttribute("class",
* currentValue -> condition(currentValue) ? "positive" : "negative"));</code>
* </p>
*
* @param name
* the name of the attribute to manipulate
* @param onAttribute
* the {@code SerializableFunction} that accepts the old value of the attribute and
* returns a new value
* @return The created behavior
*/
public static Behavior onAttribute(String name,
SerializableFunction<String, CharSequence> onAttribute)
{
Args.notEmpty(name, "name");
Args.notNull(onAttribute, "onAttribute");
return new Behavior()
{
private static final long serialVersionUID = 1L;
@Override
public void onComponentTag(Component component, ComponentTag tag)
{
if (tag.getType() != TagType.CLOSE)
{
String oldValue = tag.getAttribute(name);
tag.put(name, onAttribute.apply(oldValue));
}
}
};
}
}