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.ajax.form; 018 019import java.util.Locale; 020 021import org.apache.wicket.Application; 022import org.apache.wicket.Component; 023import org.apache.wicket.WicketRuntimeException; 024import org.apache.wicket.ajax.AjaxEventBehavior; 025import org.apache.wicket.ajax.AjaxRequestTarget; 026import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; 027import org.apache.wicket.ajax.attributes.AjaxRequestAttributes.Method; 028import org.apache.wicket.markup.html.form.FormComponent; 029import org.apache.wicket.markup.html.form.validation.IFormValidator; 030import org.apache.wicket.util.lang.Args; 031import org.danekja.java.util.function.serializable.SerializableConsumer; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035/** 036 * A behavior that updates the hosting FormComponent via ajax when an event it is attached to is 037 * triggered. This behavior encapsulates the entire form-processing workflow as relevant only to 038 * this component so if validation is successful the component's model will be updated according to 039 * the submitted value. 040 * <p> 041 * NOTE: This behavior does not validate any {@link IFormValidator}s attached to this form even 042 * though they may reference the component being updated. 043 * <p> 044 * NOTE: This behavior does not work on Choices or Groups use the 045 * {@link AjaxFormChoiceComponentUpdatingBehavior} for that. 046 * 047 * @since 1.2 048 * 049 * @author Igor Vaynberg (ivaynberg) 050 * @see #onUpdate(org.apache.wicket.ajax.AjaxRequestTarget) 051 * @see #onError(org.apache.wicket.ajax.AjaxRequestTarget, RuntimeException) 052 */ 053public abstract class AjaxFormComponentUpdatingBehavior extends AjaxEventBehavior 054{ 055 private static final Logger log = LoggerFactory 056 .getLogger(AjaxFormComponentUpdatingBehavior.class); 057 058 private static final long serialVersionUID = 1L; 059 060 /** 061 * Construct. 062 * 063 * @param event 064 * event to trigger this behavior 065 */ 066 public AjaxFormComponentUpdatingBehavior(final String event) 067 { 068 super(event); 069 } 070 071 @Override 072 protected void onBind() 073 { 074 super.onBind(); 075 076 Component component = getComponent(); 077 if (!(component instanceof FormComponent)) 078 { 079 throw new WicketRuntimeException("Behavior " + getClass().getName() 080 + " can only be added to an instance of a FormComponent"); 081 } 082 083 checkComponent((FormComponent<?>)component); 084 } 085 086 /** 087 * Check the component this behavior is bound to. 088 * <p> 089 * Logs a warning in development mode when an {@link AjaxFormChoiceComponentUpdatingBehavior} 090 * should be used. 091 * 092 * @param component 093 * bound component 094 */ 095 protected void checkComponent(FormComponent<?> component) 096 { 097 if (Application.get().usesDevelopmentConfig() 098 && AjaxFormChoiceComponentUpdatingBehavior.appliesTo(component)) 099 { 100 log.warn(String 101 .format( 102 "AjaxFormComponentUpdatingBehavior is not supposed to be added in the form component at path: \"%s\". " 103 + "Use the AjaxFormChoiceComponentUpdatingBehavior instead, that is meant for choices/groups that are not one component in the html but many", 104 component.getPageRelativePath())); 105 } 106 } 107 108 /** 109 * 110 * @return FormComponent 111 */ 112 protected final FormComponent<?> getFormComponent() 113 { 114 return (FormComponent<?>)getComponent(); 115 } 116 117 @Override 118 protected void updateAjaxAttributes(AjaxRequestAttributes attributes) 119 { 120 super.updateAjaxAttributes(attributes); 121 122 attributes.setMethod(Method.POST); 123 } 124 125 @Override 126 protected final void onEvent(final AjaxRequestTarget target) 127 { 128 final FormComponent<?> formComponent = getFormComponent(); 129 130 if ("blur".equals(getEvent().toLowerCase(Locale.ROOT)) && disableFocusOnBlur()) 131 { 132 target.focusComponent(null); 133 } 134 135 try 136 { 137 formComponent.inputChanged(); 138 formComponent.validate(); 139 if (formComponent.isValid()) 140 { 141 if (getUpdateModel()) 142 { 143 formComponent.valid(); 144 formComponent.updateModel(); 145 } 146 147 onUpdate(target); 148 } 149 else 150 { 151 formComponent.invalid(); 152 153 onError(target, null); 154 } 155 } 156 catch (RuntimeException e) 157 { 158 onError(target, e); 159 } 160 formComponent.updateAutoLabels(target); 161 } 162 163 /** 164 * Gives the control to the application to decide whether the form component model should 165 * be updated automatically or not. Make sure to call {@link org.apache.wicket.markup.html.form.FormComponent#valid()} 166 * additionally in case the application want to update the model manually. 167 * 168 * @return true if the model of form component should be updated, false otherwise 169 */ 170 protected boolean getUpdateModel() 171 { 172 return true; 173 } 174 175 /** 176 * Determines whether the focus will not be restored when the event is blur. By default this is 177 * true, as we don't want to re-focus component on blur event. 178 * 179 * @return <code>true</code> if refocusing should be disabled, <code>false</code> otherwise 180 */ 181 protected boolean disableFocusOnBlur() 182 { 183 return true; 184 } 185 186 /** 187 * Listener invoked on the ajax request. This listener is invoked after the component's model 188 * has been updated. 189 * <p> 190 * Note: {@link #onError(AjaxRequestTarget, RuntimeException)} is called instead when processing 191 * of the {@link FormComponent} failed with conversion or validation errors! 192 * 193 * @param target 194 * the current request handler 195 */ 196 protected abstract void onUpdate(AjaxRequestTarget target); 197 198 /** 199 * Called to handle any error resulting from updating form component. Errors thrown from 200 * {@link #onUpdate(org.apache.wicket.ajax.AjaxRequestTarget)} will not be caught here. 201 * 202 * The RuntimeException will be null if it was just a validation or conversion error of the 203 * FormComponent 204 * 205 * @param target 206 * the current request handler 207 * @param e 208 * the error that occurred during the update of the component 209 */ 210 protected void onError(AjaxRequestTarget target, RuntimeException e) 211 { 212 if (e != null) 213 { 214 throw e; 215 } 216 } 217 218 /** 219 * Creates an {@link AjaxFormComponentUpdatingBehavior} based on lambda expressions 220 * 221 * @param eventName 222 * the event name 223 * @param onUpdate 224 * the {@code SerializableConsumer} which accepts the {@link AjaxRequestTarget} 225 * @return the {@link AjaxFormComponentUpdatingBehavior} 226 */ 227 public static AjaxFormComponentUpdatingBehavior onUpdate(String eventName, 228 SerializableConsumer<AjaxRequestTarget> onUpdate) 229 { 230 Args.notNull(onUpdate, "onUpdate"); 231 232 return new AjaxFormComponentUpdatingBehavior(eventName) 233 { 234 private static final long serialVersionUID = 1L; 235 236 @Override 237 protected void onUpdate(AjaxRequestTarget target) 238 { 239 onUpdate.accept(target); 240 } 241 }; 242 } 243}