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}