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