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 java.util.Locale;
020
021import org.apache.wicket.Application;
022import org.apache.wicket.Component;
023import org.apache.wicket.WicketRuntimeException;
024import org.apache.wicket.ajax.AjaxEventBehavior;
025import org.apache.wicket.ajax.AjaxRequestTarget;
026import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
027import org.apache.wicket.ajax.attributes.AjaxRequestAttributes.Method;
028import org.apache.wicket.markup.html.form.FormComponent;
029import org.apache.wicket.markup.html.form.validation.IFormValidator;
030import org.apache.wicket.util.lang.Args;
031import org.danekja.java.util.function.serializable.SerializableConsumer;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035/**
036 * A behavior that updates the hosting FormComponent via ajax when an event it is attached to is
037 * triggered. This behavior encapsulates the entire form-processing workflow as relevant only to
038 * this component so if validation is successful the component's model will be updated according to
039 * the submitted value.
040 * <p>
041 * NOTE: This behavior does not validate any {@link IFormValidator}s attached to this form even
042 * though they may reference the component being updated.
043 * <p>
044 * NOTE: This behavior does not work on Choices or Groups use the
045 * {@link AjaxFormChoiceComponentUpdatingBehavior} for that.
046 * 
047 * @since 1.2
048 * 
049 * @author Igor Vaynberg (ivaynberg)
050 * @see #onUpdate(org.apache.wicket.ajax.AjaxRequestTarget)
051 * @see #onError(org.apache.wicket.ajax.AjaxRequestTarget, RuntimeException)
052 */
053public abstract class AjaxFormComponentUpdatingBehavior extends AjaxEventBehavior
054{
055        private static final Logger log = LoggerFactory
056                .getLogger(AjaxFormComponentUpdatingBehavior.class);
057
058        private static final long serialVersionUID = 1L;
059
060        /**
061         * Construct.
062         * 
063         * @param event
064         *            event to trigger this behavior
065         */
066        public AjaxFormComponentUpdatingBehavior(final String event)
067        {
068                super(event);
069        }
070
071        @Override
072        protected void onBind()
073        {
074                super.onBind();
075
076                Component component = getComponent();
077                if (!(component instanceof FormComponent))
078                {
079                        throw new WicketRuntimeException("Behavior " + getClass().getName()
080                                + " can only be added to an instance of a FormComponent");
081                }
082
083                checkComponent((FormComponent<?>)component);
084        }
085
086        /**
087         * Check the component this behavior is bound to.
088         * <p>
089         * Logs a warning in development mode when an {@link AjaxFormChoiceComponentUpdatingBehavior}
090         * should be used.
091         * 
092         * @param component
093         *            bound component
094         */
095        protected void checkComponent(FormComponent<?> component)
096        {
097                if (Application.get().usesDevelopmentConfig()
098                        && AjaxFormChoiceComponentUpdatingBehavior.appliesTo(component))
099                {
100                        log.warn(String
101                                .format(
102                                        "AjaxFormComponentUpdatingBehavior is not supposed to be added in the form component at path: \"%s\". "
103                                                + "Use the AjaxFormChoiceComponentUpdatingBehavior instead, that is meant for choices/groups that are not one component in the html but many",
104                                        component.getPageRelativePath()));
105                }
106        }
107
108        /**
109         * 
110         * @return FormComponent
111         */
112        protected final FormComponent<?> getFormComponent()
113        {
114                return (FormComponent<?>)getComponent();
115        }
116
117        @Override
118        protected void updateAjaxAttributes(AjaxRequestAttributes attributes)
119        {
120                super.updateAjaxAttributes(attributes);
121
122                attributes.setMethod(Method.POST);
123        }
124
125        @Override
126        protected final void onEvent(final AjaxRequestTarget target)
127        {
128                final FormComponent<?> formComponent = getFormComponent();
129
130                if ("blur".equals(getEvent().toLowerCase(Locale.ROOT)) && disableFocusOnBlur())
131                {
132                        target.focusComponent(null);
133                }
134
135                try
136                {
137                        formComponent.inputChanged();
138                        formComponent.validate();
139                        if (formComponent.isValid())
140                        {
141                                if (getUpdateModel())
142                                {
143                                        formComponent.valid();
144                                        formComponent.updateModel();
145                                }
146
147                                onUpdate(target);
148                        }
149                        else
150                        {
151                                formComponent.invalid();
152
153                                onError(target, null);
154                        }
155                }
156                catch (RuntimeException e)
157                {
158                        onError(target, e);
159                }
160                formComponent.updateAutoLabels(target);
161        }
162
163        /**
164         * Gives the control to the application to decide whether the form component model should
165         * be updated automatically or not. Make sure to call {@link org.apache.wicket.markup.html.form.FormComponent#valid()}
166         * additionally in case the application want to update the model manually.
167         *
168         * @return true if the model of form component should be updated, false otherwise
169         */
170        protected boolean getUpdateModel()
171        {
172                return true;
173        }
174
175        /**
176         * Determines whether the focus will not be restored when the event is blur. By default this is
177         * true, as we don't want to re-focus component on blur event.
178         * 
179         * @return <code>true</code> if refocusing should be disabled, <code>false</code> otherwise
180         */
181        protected boolean disableFocusOnBlur()
182        {
183                return true;
184        }
185
186        /**
187         * Listener invoked on the ajax request. This listener is invoked after the component's model
188         * has been updated.
189         * <p>
190         * Note: {@link #onError(AjaxRequestTarget, RuntimeException)} is called instead when processing
191         * of the {@link FormComponent} failed with conversion or validation errors!
192         * 
193         * @param target
194         *            the current request handler
195         */
196        protected abstract void onUpdate(AjaxRequestTarget target);
197
198        /**
199         * Called to handle any error resulting from updating form component. Errors thrown from
200         * {@link #onUpdate(org.apache.wicket.ajax.AjaxRequestTarget)} will not be caught here.
201         *
202         * The RuntimeException will be null if it was just a validation or conversion error of the
203         * FormComponent
204         *
205         * @param target
206         *            the current request handler
207         * @param e
208         *            the error that occurred during the update of the component
209         */
210        protected void onError(AjaxRequestTarget target, RuntimeException e)
211        {
212                if (e != null)
213                {
214                        throw e;
215                }
216        }
217
218        /**
219         * Creates an {@link AjaxFormComponentUpdatingBehavior} based on lambda expressions
220         * 
221         * @param eventName
222         *            the event name
223         * @param onUpdate
224         *            the {@code SerializableConsumer} which accepts the {@link AjaxRequestTarget}
225         * @return the {@link AjaxFormComponentUpdatingBehavior}
226         */
227        public static AjaxFormComponentUpdatingBehavior onUpdate(String eventName,
228                SerializableConsumer<AjaxRequestTarget> onUpdate)
229        {
230                Args.notNull(onUpdate, "onUpdate");
231
232                return new AjaxFormComponentUpdatingBehavior(eventName)
233                {
234                        private static final long serialVersionUID = 1L;
235
236                        @Override
237                        protected void onUpdate(AjaxRequestTarget target)
238                        {
239                                onUpdate.accept(target);
240                        }
241                };
242        }
243}