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}