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 org.apache.wicket.Component; 020import org.apache.wicket.ajax.AjaxEventBehavior; 021import org.apache.wicket.ajax.AjaxRequestTarget; 022import org.apache.wicket.ajax.attributes.AjaxCallListener; 023import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; 024import org.apache.wicket.ajax.attributes.AjaxRequestAttributes.Method; 025import org.apache.wicket.markup.html.form.Button; 026import org.apache.wicket.markup.html.form.Form; 027import org.apache.wicket.markup.html.form.IFormSubmitter; 028import org.apache.wicket.markup.html.form.IFormSubmittingComponent; 029import org.apache.wicket.util.lang.Args; 030import org.danekja.java.util.function.serializable.SerializableConsumer; 031 032/** 033 * Ajax event behavior that submits a form via ajax when the event it is attached to, is invoked. 034 * <p> 035 * The form must have an id attribute in the markup or have MarkupIdSetter added. 036 * 037 * @see AjaxEventBehavior 038 * 039 * @since 1.2 040 * 041 * @author Igor Vaynberg (ivaynberg) 042 * @see #onSubmit(org.apache.wicket.ajax.AjaxRequestTarget) 043 * @see #onAfterSubmit(org.apache.wicket.ajax.AjaxRequestTarget) 044 * @see #onError(org.apache.wicket.ajax.AjaxRequestTarget) 045 */ 046public abstract class AjaxFormSubmitBehavior extends AjaxEventBehavior 047{ 048 private static final long serialVersionUID = 1L; 049 050 /** 051 * should never be accessed directly (thus the __ cause its overkill to create a super class), 052 * instead always use #getForm() 053 */ 054 private Form<?> __form; 055 056 private boolean defaultProcessing = true; 057 058 /** 059 * Constructor. This constructor can only be used when the component this behavior is attached 060 * to is inside a form. 061 * 062 * @param event 063 * javascript event this behavior is attached to, like onclick 064 */ 065 public AjaxFormSubmitBehavior(String event) 066 { 067 this(null, event); 068 } 069 070 /** 071 * Construct. 072 * 073 * @param form 074 * form that will be submitted 075 * @param event 076 * javascript event this behavior is attached to, like onclick 077 */ 078 public AjaxFormSubmitBehavior(Form<?> form, String event) 079 { 080 super(event); 081 __form = form; 082 083 if (form != null) 084 { 085 form.setOutputMarkupId(true); 086 } 087 } 088 089 /** 090 * @return Form that will be submitted by this behavior 091 */ 092 public final Form<?> getForm() 093 { 094 if (__form == null) 095 { 096 __form = findForm(); 097 098 if (__form == null) 099 { 100 throw new IllegalStateException( 101 "form was not specified in the constructor and cannot " + 102 "be found in the hierarchy of the component this behavior " + 103 "is attached to: Component=" + getComponent().toString(false)); 104 } 105 } 106 return __form; 107 } 108 109 /** 110 * @return the bound component if it implements {@link org.apache.wicket.markup.html.form.IFormSubmittingComponent}, 111 * otherwise - {@code null} 112 */ 113 private IFormSubmittingComponent getFormSubmittingComponent() 114 { 115 IFormSubmittingComponent submittingComponent = null; 116 Component component = getComponent(); 117 if (component instanceof IFormSubmittingComponent) 118 { 119 submittingComponent = ((IFormSubmittingComponent) component); 120 } 121 return submittingComponent; 122 } 123 124 /** 125 * Finds form that will be submitted 126 * 127 * @return form to submit or {@code null} if none found 128 */ 129 protected Form<?> findForm() 130 { 131 // try to find form in the hierarchy of owning component 132 Component component = getComponent(); 133 if (component instanceof Form<?>) 134 { 135 return (Form<?>)component; 136 } 137 else 138 { 139 return component.findParent(Form.class); 140 } 141 } 142 143 /** 144 * Controls whether or not a JS <code>submit</code> should be triggered on the submitting form. 145 * False by default. 146 * 147 * @return true if <code>submit</code> should be triggered, false otherwise 148 */ 149 protected boolean shouldTriggerJavaScriptSubmitEvent() 150 { 151 return false; 152 } 153 154 @Override 155 protected void updateAjaxAttributes(AjaxRequestAttributes attributes) 156 { 157 super.updateAjaxAttributes(attributes); 158 159 Form<?> form = getForm(); 160 attributes.setFormId(form.getMarkupId()); 161 162 String formMethod = form.getMarkupAttributes().getString("method"); 163 if (formMethod == null || "POST".equalsIgnoreCase(formMethod)) 164 { 165 attributes.setMethod(Method.POST); 166 } 167 168 if (form.getRootForm().isMultiPart()) 169 { 170 attributes.setMultipart(true); 171 attributes.setMethod(Method.POST); 172 } 173 174 IFormSubmittingComponent submittingComponent = getFormSubmittingComponent(); 175 if (submittingComponent != null) 176 { 177 String submittingComponentName = submittingComponent.getInputName(); 178 attributes.setSubmittingComponentName(submittingComponentName); 179 } 180 181 if (shouldTriggerJavaScriptSubmitEvent()) { 182 attributes.getAjaxCallListeners().add(new AjaxCallListener() { 183 @Override 184 public CharSequence getPrecondition(Component component) 185 { 186 return String.format("var p, f = jQuery('#%s'), fn = function(e) { p = e.isDefaultPrevented(); e.preventDefault(); };" // 187 + "f.on('submit',fn);" // 188 + "f.trigger('submit');" // 189 + "f.off('submit',fn);" // 190 + "return !p;", form.getMarkupId()); 191 } 192 }); 193 } 194 } 195 196 @Override 197 protected void onEvent(final AjaxRequestTarget target) 198 { 199 AjaxFormSubmitBehavior.AjaxFormSubmitter submitter = new AjaxFormSubmitBehavior.AjaxFormSubmitter(this, target); 200 Form<?> form = getForm(); 201 202 form.getRootForm().onFormSubmitted(submitter); 203 } 204 205 /** 206 * A publicly reachable class that allows to introspect the submitter, e.g. to 207 * check what is the input name of the submitting component if there is such. 208 */ 209 public static class AjaxFormSubmitter implements IFormSubmitter 210 { 211 private final AjaxFormSubmitBehavior submitBehavior; 212 private final AjaxRequestTarget target; 213 214 private AjaxFormSubmitter(AjaxFormSubmitBehavior submitBehavior, AjaxRequestTarget target) 215 { 216 this.submitBehavior = submitBehavior; 217 this.target = target; 218 } 219 220 @Override 221 public Form<?> getForm() 222 { 223 return submitBehavior.getForm(); 224 } 225 226 /** 227 * @return the {@link IFormSubmittingComponent} 228 */ 229 public IFormSubmittingComponent getFormSubmittingComponent() 230 { 231 return submitBehavior.getFormSubmittingComponent(); 232 } 233 234 @Override 235 public boolean getDefaultFormProcessing() 236 { 237 return submitBehavior.getDefaultProcessing(); 238 } 239 240 @Override 241 public void onError() 242 { 243 submitBehavior.onError(target); 244 } 245 246 @Override 247 public void onSubmit() 248 { 249 submitBehavior.onSubmit(target); 250 } 251 252 @Override 253 public void onAfterSubmit() 254 { 255 submitBehavior.onAfterSubmit(target); 256 } 257 } 258 259 /** 260 * Override this method to provide special submit handling in a multi-button form. This method 261 * will be called <em>after</em> the form's onSubmit method. 262 * @param target the {@link AjaxRequestTarget} 263 */ 264 protected void onAfterSubmit(AjaxRequestTarget target) 265 { 266 } 267 268 /** 269 * Override this method to provide special submit handling in a multi-button form. This method 270 * will be called <em>before</em> the form's onSubmit method. 271 * @param target the {@link AjaxRequestTarget} 272 */ 273 protected void onSubmit(AjaxRequestTarget target) 274 { 275 } 276 277 278 /** 279 * Listener method invoked when the form has been processed and errors occurred 280 * 281 * @param target 282 */ 283 protected void onError(AjaxRequestTarget target) 284 { 285 } 286 287 /** 288 * @see Button#getDefaultFormProcessing() 289 * 290 * @return {@code true} for default processing 291 */ 292 public boolean getDefaultProcessing() 293 { 294 return defaultProcessing; 295 } 296 297 /** 298 * @see Button#setDefaultFormProcessing(boolean) 299 * @param defaultProcessing 300 */ 301 public void setDefaultProcessing(boolean defaultProcessing) 302 { 303 this.defaultProcessing = defaultProcessing; 304 } 305 306 /** 307 * Creates an {@link AjaxFormSubmitBehavior} based on lambda expressions 308 * 309 * @param eventName 310 * the event name 311 * @param onSubmit 312 * the {@code SerializableConsumer} which accepts the {@link AjaxRequestTarget} 313 * @return the {@link AjaxFormSubmitBehavior} 314 */ 315 public static AjaxFormSubmitBehavior onSubmit(String eventName, 316 SerializableConsumer<AjaxRequestTarget> onSubmit) 317 { 318 Args.notNull(onSubmit, "onSubmit"); 319 320 return new AjaxFormSubmitBehavior(eventName) 321 { 322 private static final long serialVersionUID = 1L; 323 324 @Override 325 protected void onSubmit(AjaxRequestTarget target) 326 { 327 onSubmit.accept(target); 328 } 329 }; 330 } 331}