001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.behavior; 018 019import org.apache.wicket.Application; 020import org.apache.wicket.Component; 021import org.apache.wicket.IComponentAwareEventSink; 022import org.apache.wicket.IRequestListener; 023import org.apache.wicket.event.IEvent; 024import org.apache.wicket.markup.ComponentTag; 025import org.apache.wicket.markup.head.IHeaderResponse; 026import org.apache.wicket.markup.html.IComponentAwareHeaderContributor; 027import org.apache.wicket.markup.parser.XmlTag.TagType; 028import org.apache.wicket.util.io.IClusterable; 029import org.apache.wicket.util.lang.Args; 030import org.danekja.java.util.function.serializable.SerializableBiConsumer; 031import org.danekja.java.util.function.serializable.SerializableFunction; 032 033/** 034 * Behaviors are kind of plug-ins for Components. They allow functionality to be added to a 035 * component and get essential events forwarded by the component. They can be bound to a concrete 036 * component (using the bind method which is called when the behavior is attached), but they don't 037 * need to. They can modify the components markup by changing the rendered ComponentTag. Behaviors 038 * can have their own models as well, and they are notified when these are to be detached by the 039 * component. 040 * <p> 041 * You also cannot modify a components model with a behavior. 042 * </p> 043 * 044 * @see IRequestListener 045 * @see org.apache.wicket.markup.html.IHeaderContributor 046 * @see org.apache.wicket.behavior.AbstractAjaxBehavior 047 * @see org.apache.wicket.AttributeModifier 048 * 049 * @author Ralf Ebert 050 * @author Eelco Hillenius 051 * @author Igor Vaynberg (ivaynberg) 052 */ 053public abstract class Behavior 054 implements 055 IClusterable, 056 IComponentAwareEventSink, 057 IComponentAwareHeaderContributor 058{ 059 private static final long serialVersionUID = 1L; 060 061 /** 062 * Constructor 063 */ 064 public Behavior() 065 { 066 if (Application.exists()) 067 { 068 Application.get().getBehaviorInstantiationListeners().onInstantiation(this); 069 } 070 } 071 072 /** 073 * Called when a component is about to render. 074 * 075 * @param component 076 * the component that has this behavior coupled 077 */ 078 public void beforeRender(Component component) 079 { 080 } 081 082 /** 083 * Called when a component that has this behavior coupled was rendered. 084 * 085 * @param component 086 * the component that has this behavior coupled 087 */ 088 public void afterRender(Component component) 089 { 090 } 091 092 /** 093 * Bind this handler to the given component. This method is called by the host component 094 * immediately after this behavior is added to it. This method is useful if you need to do 095 * initialization based on the component it is attached and you can't wait to do it at render 096 * time. Keep in mind that if you decide to keep a reference to the host component, it is not 097 * thread safe anymore, and should thus only be used in situations where you do not reuse the 098 * behavior for multiple components. 099 * 100 * @param component 101 * the component to bind to 102 */ 103 public void bind(Component component) 104 { 105 } 106 107 /** 108 * Notifies the behavior it is removed from the specified component 109 * 110 * @param component 111 * the component this behavior is unbound from 112 */ 113 public void unbind(Component component) 114 { 115 } 116 117 /** 118 * Allows the behavior to detach any state it has attached during request processing. 119 * 120 * @param component 121 * the component that initiates the detachment of this behavior 122 */ 123 public void detach(Component component) 124 { 125 } 126 127 /** 128 * In case an unexpected exception happened anywhere between 129 * {@linkplain #onComponentTag(org.apache.wicket.Component, org.apache.wicket.markup.ComponentTag)} and 130 * {@linkplain #afterRender(org.apache.wicket.Component)}, 131 * onException() will be called for any behavior. Typically, if you clean up resources in 132 * {@link #afterRender(Component)}, you should do the same in the implementation of this method. 133 * 134 * @param component 135 * the component that has a reference to this behavior and during which processing 136 * the exception occurred 137 * @param exception 138 * the unexpected exception 139 */ 140 public void onException(Component component, RuntimeException exception) 141 { 142 } 143 144 /** 145 * This method returns false if the behavior generates a callback url (for example ajax 146 * behaviors) 147 * 148 * @param component 149 * the component that has this behavior coupled. 150 * 151 * @return boolean true or false. 152 */ 153 public boolean getStatelessHint(Component component) 154 { 155 if (this instanceof IRequestListener) 156 { 157 // this behavior implements a callback interface, so it cannot be stateless 158 return false; 159 } 160 return true; 161 } 162 163 /** 164 * Called when a components is rendering and wants to render this behavior. If false is returned 165 * this behavior will be ignored. 166 * 167 * @param component 168 * the component that has this behavior coupled 169 * 170 * @return true if this behavior must be executed/rendered 171 */ 172 public boolean isEnabled(Component component) 173 { 174 return true; 175 } 176 177 /** 178 * Called any time a component that has this behavior registered is rendering the component tag. 179 * 180 * @param component 181 * the component that renders this tag currently 182 * @param tag 183 * the tag that is rendered 184 */ 185 public void onComponentTag(Component component, ComponentTag tag) 186 { 187 } 188 189 /** 190 * Specifies whether or not this behavior is temporary. Temporary behaviors are removed at the 191 * end of request and never reattached. Such behaviors are useful for modifying component 192 * rendering only when it renders next. Usecases include javascript effects, initial clientside 193 * dom setup, etc. 194 * 195 * @param component 196 * 197 * @return true if this behavior is temporary 198 */ 199 public boolean isTemporary(Component component) 200 { 201 return false; 202 } 203 204 /** 205 * Checks whether or not an {@link IRequestListener} can be invoked on this behavior. For further 206 * information please read the javadoc on {@link Component#canCallListener()}, 207 * this method has the same semantics. 208 * 209 * WARNING: Read the javadoc of {@link Component#canCallListener()} for important 210 * security-related information. 211 * 212 * @param component 213 * component this behavior is attached to 214 * @return {@literal true} iff the listener method can be invoked 215 */ 216 public boolean canCallListener(Component component) 217 { 218 return isEnabled(component) && component.canCallListener(); 219 } 220 221 222 /** 223 * Render to the web response whatever the component wants to contribute to the head section. 224 * 225 * @param component 226 * 227 * @param response 228 * Response object 229 */ 230 @Override 231 public void renderHead(Component component, IHeaderResponse response) 232 { 233 } 234 235 /** 236 * Called immediately after the onConfigure method in a component. Since this is before the 237 * rendering cycle has begun, the behavior can modify the configuration of the component (i.e. 238 * setVisible(false)) 239 * 240 * @param component 241 * the component being configured 242 */ 243 public void onConfigure(Component component) 244 { 245 } 246 247 /** 248 * Called to notify the behavior about any events sent to the component 249 * 250 * @see org.apache.wicket.IComponentAwareEventSink#onEvent(org.apache.wicket.Component, 251 * org.apache.wicket.event.IEvent) 252 */ 253 @Override 254 public void onEvent(Component component, IEvent<?> event) 255 { 256 } 257 258 /** 259 * Called to notify that the component is being removed from its parent 260 * @param component 261 * the removed component 262 */ 263 public void onRemove(Component component) 264 { 265 } 266 267 /** 268 * Creates a {@link Behavior} that uses the given {@code SerializableConsumer consumer} to do 269 * something with the component's tag. 270 * 271 * <p> 272 * Usage:<br/> 273 * <code>component.add(onTag(tag -> tag.put(key, value)));</code> 274 * </p> 275 * 276 * @param onTagConsumer 277 * the {@code SerializableConsumer} that accepts the {@link ComponentTag} 278 * @return The created behavior 279 */ 280 public static Behavior onTag(SerializableBiConsumer<Component, ComponentTag> onTagConsumer) 281 { 282 Args.notNull(onTagConsumer, "onTagConsumer"); 283 284 return new Behavior() 285 { 286 @Override 287 public void onComponentTag(Component component, ComponentTag tag) 288 { 289 onTagConsumer.accept(component, tag); 290 } 291 }; 292 } 293 294 /** 295 * Creates a {@link Behavior} that uses the given {@code SerializableFunction function} to do 296 * something with a component's attribute. 297 * 298 * <p> 299 * Usage:<br/> 300 * <code>component.add(onAttribute("class", 301 * currentValue -> condition(currentValue) ? "positive" : "negative"));</code> 302 * </p> 303 * 304 * @param name 305 * the name of the attribute to manipulate 306 * @param onAttribute 307 * the {@code SerializableFunction} that accepts the old value of the attribute and 308 * returns a new value 309 * @return The created behavior 310 */ 311 public static Behavior onAttribute(String name, 312 SerializableFunction<String, CharSequence> onAttribute) 313 { 314 Args.notEmpty(name, "name"); 315 Args.notNull(onAttribute, "onAttribute"); 316 317 return new Behavior() 318 { 319 private static final long serialVersionUID = 1L; 320 321 @Override 322 public void onComponentTag(Component component, ComponentTag tag) 323 { 324 if (tag.getType() != TagType.CLOSE) 325 { 326 String oldValue = tag.getAttribute(name); 327 tag.put(name, onAttribute.apply(oldValue)); 328 } 329 } 330 }; 331 } 332 333}