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.WicketRuntimeException; 021import org.apache.wicket.ajax.AjaxRequestTarget; 022import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; 023import org.apache.wicket.ajax.attributes.ThrottlingSettings; 024import org.apache.wicket.behavior.Behavior; 025import org.apache.wicket.feedback.IFeedback; 026import org.apache.wicket.markup.html.form.Form; 027import org.apache.wicket.markup.html.form.FormComponent; 028import org.apache.wicket.util.io.IClusterable; 029import java.time.Duration; 030import org.apache.wicket.util.visit.IVisit; 031import org.apache.wicket.util.visit.IVisitor; 032 033/** 034 * Ajax event behavior that submits the form and updates all form feedback panels on the page. 035 * Useful for providing instant feedback. 036 * 037 * Can be bound either to a {@link Form form} or to a component inside a form. 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 class AjaxFormValidatingBehavior extends Behavior 047{ 048 private static final long serialVersionUID = 1L; 049 050 private final String event; 051 private final Duration throttleDelay; 052 053 /** 054 * The form that will be submitted via ajax 055 */ 056 private Form<?> form; 057 058 /** 059 * A flag indicating whether this behavior has been rendered at least once 060 */ 061 private boolean hasBeenRendered = false; 062 063 /** 064 * Construct. 065 * 066 * @param event 067 * javascript event this behavior will be invoked on, like onclick 068 */ 069 public AjaxFormValidatingBehavior(String event) 070 { 071 this(event, null); 072 } 073 074 /** 075 * Construct. 076 * 077 * @param event 078 * javascript event this behavior will be invoked on, like onclick 079 * @param throttleDelay 080 * the duration for which the Ajax call should be throttled 081 */ 082 public AjaxFormValidatingBehavior(String event, Duration throttleDelay) 083 { 084 this.event = event; 085 this.throttleDelay = throttleDelay; 086 } 087 088 @Override 089 public void bind(Component component) 090 { 091 super.bind(component); 092 093 if (component instanceof Form<?>) 094 { 095 form = (Form<?>) component; 096 } 097 else 098 { 099 form = Form.findForm(component); 100 if (form == null) 101 { 102 throw new WicketRuntimeException(AjaxFormValidatingBehavior.class.getSimpleName() + 103 " should be bound to a Form component or a component that is inside a form!"); 104 } 105 } 106 } 107 108 @Override 109 public void onConfigure(Component component) 110 { 111 super.onConfigure(component); 112 113 if (hasBeenRendered == false) 114 { 115 hasBeenRendered = true; 116 117 form.visitChildren(FormComponent.class, new FormValidateVisitor()); 118 } 119 } 120 121 protected void onSubmit(final AjaxRequestTarget target) 122 { 123 addFeedbackPanels(target); 124 } 125 126 protected void onAfterSubmit(final AjaxRequestTarget target) 127 { 128 } 129 130 protected void onError(AjaxRequestTarget target) 131 { 132 addFeedbackPanels(target); 133 } 134 135 /** 136 * Adds all feedback panels on the page to the ajax request target so they are updated 137 * 138 * @param target 139 */ 140 protected final void addFeedbackPanels(final AjaxRequestTarget target) 141 { 142 form.getPage().visitChildren(IFeedback.class, new IVisitor<Component, Void>() 143 { 144 @Override 145 public void component(final Component component, final IVisit<Void> visit) 146 { 147 component.configure(); // feedback component might change its visibility 148 if (component.isVisibleInHierarchy()) 149 { 150 target.add(component); 151 } 152 else 153 { 154 visit.dontGoDeeper(); 155 } 156 } 157 }); 158 } 159 160 protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) 161 { 162 } 163 164 private class FormValidateVisitor implements IVisitor<FormComponent, Void>, IClusterable 165 { 166 private static final long serialVersionUID = 1L; 167 168 @Override 169 public void component(final FormComponent component, final IVisit<Void> visit) 170 { 171 final AjaxFormSubmitBehavior behavior = new AjaxFormSubmitBehavior(form, event) 172 { 173 @Override 174 protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) 175 { 176 super.updateAjaxAttributes(attributes); 177 178 if (throttleDelay != null) 179 { 180 String id = "throttle-" + component.getMarkupId(); 181 ThrottlingSettings throttlingSettings = new ThrottlingSettings(id, 182 throttleDelay); 183 attributes.setThrottlingSettings(throttlingSettings); 184 } 185 186 AjaxFormValidatingBehavior.this.updateAjaxAttributes(attributes); 187 } 188 189 @Override 190 protected void onSubmit(AjaxRequestTarget target) 191 { 192 super.onSubmit(target); 193 AjaxFormValidatingBehavior.this.onSubmit(target); 194 } 195 196 @Override 197 protected void onAfterSubmit(AjaxRequestTarget target) 198 { 199 super.onAfterSubmit(target); 200 AjaxFormValidatingBehavior.this.onAfterSubmit(target); 201 } 202 203 @Override 204 protected void onError(AjaxRequestTarget target) 205 { 206 super.onError(target); 207 AjaxFormValidatingBehavior.this.onError(target); 208 } 209 }; 210 component.add(behavior); 211 visit.dontGoDeeper(); 212 } 213 } 214}