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.markup.html.form;
018
019import java.util.Optional;
020
021import org.apache.wicket.Component;
022import org.apache.wicket.ajax.AjaxRequestTarget;
023import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
024import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
025import org.apache.wicket.markup.ComponentTag;
026import org.apache.wicket.markup.html.form.Button;
027import org.apache.wicket.markup.html.form.Form;
028import org.apache.wicket.model.IModel;
029
030/**
031 * An ajax submit button that will degrade to a normal request if ajax is not available or
032 * javascript is disabled.
033 * 
034 * @since 1.3
035 * 
036 * @author Jeremy Thomerson (jthomerson)
037 * @author Alastair Maw
038 * 
039 * 
040 */
041public abstract class AjaxFallbackButton extends Button
042{
043        private static final long serialVersionUID = 1L;
044
045        private final Form<?> mForm;
046
047        /**
048         * Construct.
049         * 
050         * @param id
051         * @param form
052         */
053        public AjaxFallbackButton(String id, Form<?> form)
054        {
055                this(id, null, form);
056        }
057
058        /**
059         * Construct.
060         * 
061         * @param id
062         * @param model
063         * @param form
064         */
065        public AjaxFallbackButton(String id, IModel<String> model, Form<?> form)
066        {
067                super(id, model);
068                mForm = form;
069
070                add(newAjaxEventBehavior(form, "click"));
071        }
072
073        protected AjaxFormSubmitBehavior newAjaxEventBehavior(Form<?> form, String event)
074        {
075                return new AjaxFormSubmitBehavior(form, event)
076                {
077                        private static final long serialVersionUID = 1L;
078
079                        @Override
080                        protected void onSubmit(AjaxRequestTarget target)
081                        {
082                                AjaxFallbackButton.this.onSubmit(Optional.of(target));
083                        }
084
085                        @Override
086                        protected void onAfterSubmit(AjaxRequestTarget target)
087                        {
088                                AjaxFallbackButton.this.onAfterSubmit(Optional.of(target));
089                        }
090
091                        @Override
092                        protected void onError(AjaxRequestTarget target)
093                        {
094                                AjaxFallbackButton.this.onError(Optional.of(target));
095                        }
096
097                        @Override
098                        public boolean getDefaultProcessing()
099                        {
100                                return AjaxFallbackButton.this.getDefaultFormProcessing();
101                        }
102
103                        @Override
104                        protected void updateAjaxAttributes(AjaxRequestAttributes attributes)
105                        {
106                                super.updateAjaxAttributes(attributes);
107
108                                // do not allow normal form submit to happen
109                                attributes.setPreventDefault(true);
110
111                                AjaxFallbackButton.this.updateAjaxAttributes(attributes);
112                        }
113
114                        @Override
115                        public boolean getStatelessHint(Component component)
116                        {
117                                return AjaxFallbackButton.this.getStatelessHint();
118                        }
119                        
120                        @Override
121                        protected boolean shouldTriggerJavaScriptSubmitEvent()
122                        {
123                                return AjaxFallbackButton.this.shouldTriggerJavaScriptSubmitEvent();
124                        }
125                };
126        }
127
128        /**
129         * Controls whether or not a JS <code>submit</code> should be triggered on the submitting form.
130         * False by default.
131         * 
132         * @return true if <code>submit</code> should be triggered, false otherwise
133         */
134        protected boolean shouldTriggerJavaScriptSubmitEvent()
135        {
136                return false;
137        }
138
139        protected void updateAjaxAttributes(AjaxRequestAttributes attributes)
140        {
141        }
142
143        /**
144         * Listener method invoked on form submit with errors. If ajax failed and this event was
145         * generated via a normal submission, the target argument will be null.
146         * 
147         * @param target
148         */
149        protected void onError(Optional<AjaxRequestTarget> target)
150        {
151        }
152
153        @Override
154        public final void onError()
155        {
156                if (getRequestCycle().find(AjaxRequestTarget.class).isPresent() == false)
157                {
158                        onError(Optional.empty());
159                }
160        }
161
162        @Override
163        public final void onSubmit()
164        {
165                if (getRequestCycle().find(AjaxRequestTarget.class).isPresent() == false)
166                {
167                        onSubmit(Optional.empty());
168                }
169        }
170
171        @Override
172        public final void onAfterSubmit()
173        {
174                if (getRequestCycle().find(AjaxRequestTarget.class).isPresent() == false)
175                {
176                        onAfterSubmit(Optional.empty());
177                }
178        }
179
180        @Override
181        public Form<?> getForm()
182        {
183                return mForm == null ? super.getForm() : mForm;
184        }
185
186        /**
187         * Callback for the onClick event. If ajax failed and this event was generated via a normal
188         * submission, the target argument will be {@link Optional#empty()}. This method will be called
189         * <em>before</em> {@link Form#onSubmit()}.
190         * 
191         * @param target
192         *            ajax target if this linked was invoked using ajax, {@link Optional#empty()}
193         *            otherwise
194         */
195        protected void onSubmit(final Optional<AjaxRequestTarget> target)
196        {
197        }
198
199        /**
200         * Callback for the onClick event. If ajax failed and this event was generated via a normal
201         * submission, the target argument will be null. This method will be called <em>after</em>
202         * {@link Form#onSubmit()}.
203         * 
204         * @param target
205         *            ajax target if this linked was invoked using ajax, null otherwise
206         */
207        protected void onAfterSubmit(final Optional<AjaxRequestTarget> target)
208        {
209        }
210
211        /**
212         * Helper methods that both checks whether the link is enabled and whether the action ENABLE is
213         * allowed.
214         * 
215         * @return whether the link should be rendered as enabled
216         */
217        protected final boolean isButtonEnabled()
218        {
219                return isEnabledInHierarchy();
220        }
221
222        @Override
223        protected void onComponentTag(ComponentTag tag)
224        {
225                String tagName = tag.getName();
226                if (!("input".equalsIgnoreCase(tagName) || "button".equalsIgnoreCase(tagName)))
227                {
228                        String msg = String.format("%s must be used only with <input type=\"submit\"> or <input type=\"submit\"> markup elements. " +
229                                        "The fallback functionality doesn't work for other markup elements. " +
230                                        "Component path: %s, markup element: <%s>.",
231                                        AjaxFallbackButton.class.getSimpleName(), getClassRelativePath(), tagName);
232                        findMarkupStream().throwMarkupException(msg);
233                }
234
235                super.onComponentTag(tag);
236        }
237}